@powersync/web 0.0.0-dev-20250526133243 → 0.0.0-dev-20250528152729

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.umd.js CHANGED
@@ -39749,9 +39749,6 @@ class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementati
39749
39749
  */
39750
39750
  async connect(options) {
39751
39751
  await this.waitForReady();
39752
- // This is needed since a new tab won't have any reference to the
39753
- // shared worker sync implementation since that is only created on the first call to `connect`.
39754
- await this.disconnect();
39755
39752
  return this.syncManager.connect(options);
39756
39753
  }
39757
39754
  async disconnect() {
@@ -39767,12 +39764,21 @@ class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementati
39767
39764
  }
39768
39765
  async dispose() {
39769
39766
  await this.waitForReady();
39770
- // Signal the shared worker that this client is closing its connection to the worker
39771
- const closeMessagePayload = {
39772
- event: _worker_sync_SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_2__.SharedSyncClientEvent.CLOSE_CLIENT,
39773
- data: {}
39774
- };
39775
- this.messagePort.postMessage(closeMessagePayload);
39767
+ await new Promise((resolve) => {
39768
+ // Listen for the close acknowledgment from the worker
39769
+ this.messagePort.addEventListener('message', (event) => {
39770
+ const payload = event.data;
39771
+ if (payload?.event === _worker_sync_SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_2__.SharedSyncClientEvent.CLOSE_ACK) {
39772
+ resolve();
39773
+ }
39774
+ });
39775
+ // Signal the shared worker that this client is closing its connection to the worker
39776
+ const closeMessagePayload = {
39777
+ event: _worker_sync_SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_2__.SharedSyncClientEvent.CLOSE_CLIENT,
39778
+ data: {}
39779
+ };
39780
+ this.messagePort.postMessage(closeMessagePayload);
39781
+ });
39776
39782
  // Release the proxy
39777
39783
  this.syncManager[comlink__WEBPACK_IMPORTED_MODULE_0__.releaseProxy]();
39778
39784
  this.messagePort.close();
@@ -40250,9 +40256,7 @@ __webpack_require__.r(__webpack_exports__);
40250
40256
  /* harmony import */ var _db_sync_WebStreamingSyncImplementation__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../db/sync/WebStreamingSyncImplementation */ "./lib/src/db/sync/WebStreamingSyncImplementation.js");
40251
40257
  /* harmony import */ var _db_adapters_LockedAsyncDatabaseAdapter__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../db/adapters/LockedAsyncDatabaseAdapter */ "./lib/src/db/adapters/LockedAsyncDatabaseAdapter.js");
40252
40258
  /* harmony import */ var _db_adapters_WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../db/adapters/WorkerWrappedAsyncDatabaseConnection */ "./lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js");
40253
- /* harmony import */ var _shared_navigator__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../../shared/navigator */ "./lib/src/shared/navigator.js");
40254
- /* harmony import */ var _BroadcastLogger__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./BroadcastLogger */ "./lib/src/worker/sync/BroadcastLogger.js");
40255
-
40259
+ /* harmony import */ var _BroadcastLogger__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./BroadcastLogger */ "./lib/src/worker/sync/BroadcastLogger.js");
40256
40260
 
40257
40261
 
40258
40262
 
@@ -40271,14 +40275,20 @@ var SharedSyncClientEvent;
40271
40275
  * close it's connection to the client.
40272
40276
  */
40273
40277
  SharedSyncClientEvent["CLOSE_CLIENT"] = "close-client";
40278
+ SharedSyncClientEvent["CLOSE_ACK"] = "close-ack";
40274
40279
  })(SharedSyncClientEvent || (SharedSyncClientEvent = {}));
40280
+ /**
40281
+ * HACK: The shared implementation wraps and provides its own
40282
+ * PowerSyncBackendConnector when generating the streaming sync implementation.
40283
+ * We provide this unused placeholder when connecting with the ConnectionManager.
40284
+ */
40285
+ const CONNECTOR_PLACEHOLDER = {};
40275
40286
  /**
40276
40287
  * @internal
40277
40288
  * Shared sync implementation which runs inside a shared webworker
40278
40289
  */
40279
40290
  class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODULE_0__.BaseObserver {
40280
40291
  ports;
40281
- syncStreamClient;
40282
40292
  isInitialized;
40283
40293
  statusListener;
40284
40294
  fetchCredentialsController;
@@ -40287,6 +40297,8 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40287
40297
  syncParams;
40288
40298
  logger;
40289
40299
  lastConnectOptions;
40300
+ portMutex;
40301
+ connectionManager;
40290
40302
  syncStatus;
40291
40303
  broadCastLogger;
40292
40304
  constructor() {
@@ -40294,9 +40306,9 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40294
40306
  this.ports = [];
40295
40307
  this.dbAdapter = null;
40296
40308
  this.syncParams = null;
40297
- this.syncStreamClient = null;
40298
40309
  this.logger = (0,_powersync_common__WEBPACK_IMPORTED_MODULE_0__.createLogger)('shared-sync');
40299
40310
  this.lastConnectOptions = undefined;
40311
+ this.portMutex = new async_mutex__WEBPACK_IMPORTED_MODULE_1__.Mutex();
40300
40312
  this.isInitialized = new Promise((resolve) => {
40301
40313
  const callback = this.registerListener({
40302
40314
  initialized: () => {
@@ -40306,21 +40318,42 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40306
40318
  });
40307
40319
  });
40308
40320
  this.syncStatus = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SyncStatus({});
40309
- this.broadCastLogger = new _BroadcastLogger__WEBPACK_IMPORTED_MODULE_8__.BroadcastLogger(this.ports);
40310
- }
40311
- async waitForStatus(status) {
40312
- await this.waitForReady();
40313
- return this.syncStreamClient.waitForStatus(status);
40314
- }
40315
- async waitUntilStatusMatches(predicate) {
40316
- await this.waitForReady();
40317
- return this.syncStreamClient.waitUntilStatusMatches(predicate);
40321
+ this.broadCastLogger = new _BroadcastLogger__WEBPACK_IMPORTED_MODULE_7__.BroadcastLogger(this.ports);
40322
+ this.connectionManager = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.ConnectionManager({
40323
+ createSyncImplementation: async (connector, options) => {
40324
+ await this.waitForReady();
40325
+ if (!this.dbAdapter) {
40326
+ await this.openInternalDB();
40327
+ }
40328
+ const sync = this.generateStreamingImplementation();
40329
+ const onDispose = sync.registerListener({
40330
+ statusChanged: (status) => {
40331
+ this.updateAllStatuses(status.toJSON());
40332
+ }
40333
+ });
40334
+ return {
40335
+ sync,
40336
+ onDispose
40337
+ };
40338
+ },
40339
+ logger: this.logger
40340
+ });
40318
40341
  }
40319
40342
  get lastSyncedAt() {
40320
- return this.syncStreamClient?.lastSyncedAt;
40343
+ return this.connectionManager.syncStreamImplementation?.lastSyncedAt;
40321
40344
  }
40322
40345
  get isConnected() {
40323
- return this.syncStreamClient?.isConnected ?? false;
40346
+ return this.connectionManager.syncStreamImplementation?.isConnected ?? false;
40347
+ }
40348
+ async waitForStatus(status) {
40349
+ return this.withSyncImplementation(async (sync) => {
40350
+ return sync.waitForStatus(status);
40351
+ });
40352
+ }
40353
+ async waitUntilStatusMatches(predicate) {
40354
+ return this.withSyncImplementation(async (sync) => {
40355
+ return sync.waitUntilStatusMatches(predicate);
40356
+ });
40324
40357
  }
40325
40358
  async waitForReady() {
40326
40359
  return this.isInitialized;
@@ -40333,25 +40366,32 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40333
40366
  * Configures the DBAdapter connection and a streaming sync client.
40334
40367
  */
40335
40368
  async setParams(params) {
40336
- if (this.syncParams) {
40337
- // Cannot modify already existing sync implementation
40338
- return;
40339
- }
40340
- this.syncParams = params;
40341
- if (params.streamOptions?.flags?.broadcastLogs) {
40342
- this.logger = this.broadCastLogger;
40343
- }
40344
- self.onerror = (event) => {
40345
- // Share any uncaught events on the broadcast logger
40346
- this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
40347
- };
40348
- await this.openInternalDB();
40349
- this.iterateListeners((l) => l.initialized?.());
40369
+ await this.portMutex.runExclusive(async () => {
40370
+ if (this.syncParams) {
40371
+ if (!this.dbAdapter) {
40372
+ await this.openInternalDB();
40373
+ }
40374
+ // Cannot modify already existing sync implementation
40375
+ return;
40376
+ }
40377
+ this.syncParams = params;
40378
+ if (params.streamOptions?.flags?.broadcastLogs) {
40379
+ this.logger = this.broadCastLogger;
40380
+ }
40381
+ self.onerror = (event) => {
40382
+ // Share any uncaught events on the broadcast logger
40383
+ this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
40384
+ };
40385
+ if (!this.dbAdapter) {
40386
+ await this.openInternalDB();
40387
+ }
40388
+ this.iterateListeners((l) => l.initialized?.());
40389
+ });
40350
40390
  }
40351
40391
  async dispose() {
40352
40392
  await this.waitForReady();
40353
40393
  this.statusListener?.();
40354
- return this.syncStreamClient?.dispose();
40394
+ return this.connectionManager.close();
40355
40395
  }
40356
40396
  /**
40357
40397
  * Connects to the PowerSync backend instance.
@@ -40360,99 +40400,103 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40360
40400
  * connects.
40361
40401
  */
40362
40402
  async connect(options) {
40363
- await this.waitForReady();
40364
- // This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
40365
- return (0,_shared_navigator__WEBPACK_IMPORTED_MODULE_7__.getNavigatorLocks)().request('shared-sync-connect', async () => {
40366
- if (!this.dbAdapter) {
40367
- await this.openInternalDB();
40368
- }
40369
- this.syncStreamClient = this.generateStreamingImplementation();
40403
+ await this.portMutex.runExclusive(async () => {
40404
+ // Keep track of the last connect options if we need to reconnect due to a lost client
40370
40405
  this.lastConnectOptions = options;
40371
- this.syncStreamClient.registerListener({
40372
- statusChanged: (status) => {
40373
- this.updateAllStatuses(status.toJSON());
40374
- }
40375
- });
40376
- await this.syncStreamClient.connect(options);
40406
+ return this.connectionManager.connect(CONNECTOR_PLACEHOLDER, options);
40377
40407
  });
40378
40408
  }
40379
40409
  async disconnect() {
40380
- await this.waitForReady();
40381
- // This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
40382
- return (0,_shared_navigator__WEBPACK_IMPORTED_MODULE_7__.getNavigatorLocks)().request('shared-sync-connect', async () => {
40383
- await this.syncStreamClient?.disconnect();
40384
- await this.syncStreamClient?.dispose();
40385
- this.syncStreamClient = null;
40410
+ await this.portMutex.runExclusive(async () => {
40411
+ await this.connectionManager.disconnect();
40386
40412
  });
40387
40413
  }
40388
40414
  /**
40389
40415
  * Adds a new client tab's message port to the list of connected ports
40390
40416
  */
40391
- addPort(port) {
40392
- const portProvider = {
40393
- port,
40394
- clientProvider: comlink__WEBPACK_IMPORTED_MODULE_2__.wrap(port)
40395
- };
40396
- this.ports.push(portProvider);
40397
- // Give the newly connected client the latest status
40398
- const status = this.syncStreamClient?.syncStatus;
40399
- if (status) {
40400
- portProvider.clientProvider.statusChanged(status.toJSON());
40401
- }
40417
+ async addPort(port) {
40418
+ await this.portMutex.runExclusive(() => {
40419
+ const portProvider = {
40420
+ port,
40421
+ clientProvider: comlink__WEBPACK_IMPORTED_MODULE_2__.wrap(port)
40422
+ };
40423
+ this.ports.push(portProvider);
40424
+ // Give the newly connected client the latest status
40425
+ const status = this.connectionManager.syncStreamImplementation?.syncStatus;
40426
+ if (status) {
40427
+ portProvider.clientProvider.statusChanged(status.toJSON());
40428
+ }
40429
+ });
40402
40430
  }
40403
40431
  /**
40404
40432
  * Removes a message port client from this manager's managed
40405
40433
  * clients.
40406
40434
  */
40407
40435
  async removePort(port) {
40408
- const index = this.ports.findIndex((p) => p.port == port);
40409
- if (index < 0) {
40410
- this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`);
40411
- return;
40412
- }
40413
- const trackedPort = this.ports[index];
40414
- // Remove from the list of active ports
40415
- this.ports.splice(index, 1);
40416
- /**
40417
- * The port might currently be in use. Any active functions might
40418
- * not resolve. Abort them here.
40419
- */
40420
- [this.fetchCredentialsController, this.uploadDataController].forEach((abortController) => {
40421
- if (abortController?.activePort.port == port) {
40422
- abortController.controller.abort(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Closing pending requests after client port is removed'));
40436
+ return await this.portMutex.runExclusive(async () => {
40437
+ const index = this.ports.findIndex((p) => p.port == port);
40438
+ if (index < 0) {
40439
+ this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`);
40440
+ return;
40423
40441
  }
40424
- });
40425
- const shouldReconnect = !!this.syncStreamClient;
40426
- if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
40427
- if (shouldReconnect) {
40428
- await this.disconnect();
40442
+ const trackedPort = this.ports[index];
40443
+ // Remove from the list of active ports
40444
+ this.ports.splice(index, 1);
40445
+ /**
40446
+ * The port might currently be in use. Any active functions might
40447
+ * not resolve. Abort them here.
40448
+ */
40449
+ [this.fetchCredentialsController, this.uploadDataController].forEach((abortController) => {
40450
+ if (abortController?.activePort.port == port) {
40451
+ abortController.controller.abort(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Closing pending requests after client port is removed'));
40452
+ }
40453
+ });
40454
+ const shouldReconnect = !!this.connectionManager.syncStreamImplementation && this.ports.length > 0;
40455
+ if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
40456
+ if (shouldReconnect) {
40457
+ await this.connectionManager.disconnect();
40458
+ }
40459
+ // Clearing the adapter will result in a new one being opened in connect
40460
+ this.dbAdapter = null;
40461
+ if (shouldReconnect) {
40462
+ await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions);
40463
+ }
40429
40464
  }
40430
- // Clearing the adapter will result in a new one being opened in connect
40431
- this.dbAdapter = null;
40432
- if (shouldReconnect) {
40433
- await this.connect(this.lastConnectOptions);
40465
+ if (trackedPort.db) {
40466
+ await trackedPort.db.close();
40434
40467
  }
40435
- }
40436
- if (trackedPort.db) {
40437
- trackedPort.db.close();
40438
- }
40439
- // Release proxy
40440
- trackedPort.clientProvider[comlink__WEBPACK_IMPORTED_MODULE_2__.releaseProxy]();
40468
+ this.logger.debug(`Port ${port} removed from shared sync implementation.`);
40469
+ // Release proxy
40470
+ return () => trackedPort.clientProvider[comlink__WEBPACK_IMPORTED_MODULE_2__.releaseProxy]();
40471
+ });
40441
40472
  }
40442
40473
  triggerCrudUpload() {
40443
- this.waitForReady().then(() => this.syncStreamClient?.triggerCrudUpload());
40444
- }
40445
- async obtainLock(lockOptions) {
40446
- await this.waitForReady();
40447
- return this.syncStreamClient.obtainLock(lockOptions);
40474
+ this.waitForReady().then(() => this.connectionManager.syncStreamImplementation?.triggerCrudUpload());
40448
40475
  }
40449
40476
  async hasCompletedSync() {
40450
- await this.waitForReady();
40451
- return this.syncStreamClient.hasCompletedSync();
40477
+ return this.withSyncImplementation(async (sync) => {
40478
+ return sync.hasCompletedSync();
40479
+ });
40452
40480
  }
40453
40481
  async getWriteCheckpoint() {
40482
+ return this.withSyncImplementation(async (sync) => {
40483
+ return sync.getWriteCheckpoint();
40484
+ });
40485
+ }
40486
+ async withSyncImplementation(callback) {
40454
40487
  await this.waitForReady();
40455
- return this.syncStreamClient.getWriteCheckpoint();
40488
+ if (this.connectionManager.syncStreamImplementation) {
40489
+ return callback(this.connectionManager.syncStreamImplementation);
40490
+ }
40491
+ const sync = await new Promise((resolve) => {
40492
+ const dispose = this.connectionManager.registerListener({
40493
+ syncStreamCreated: (sync) => {
40494
+ resolve(sync);
40495
+ dispose?.();
40496
+ }
40497
+ });
40498
+ });
40499
+ return callback(sync);
40456
40500
  }
40457
40501
  generateStreamingImplementation() {
40458
40502
  // This should only be called after initialization has completed
@@ -40556,12 +40600,12 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40556
40600
  * sync stream client and all tab client's sync status
40557
40601
  */
40558
40602
  _testUpdateAllStatuses(status) {
40559
- if (!this.syncStreamClient) {
40603
+ if (!this.connectionManager.syncStreamImplementation) {
40560
40604
  // This is just for testing purposes
40561
- this.syncStreamClient = this.generateStreamingImplementation();
40605
+ this.connectionManager.syncStreamImplementation = this.generateStreamingImplementation();
40562
40606
  }
40563
40607
  // Only assigning, don't call listeners for this test
40564
- this.syncStreamClient.syncStatus = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SyncStatus(status);
40608
+ this.connectionManager.syncStreamImplementation.syncStatus = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SyncStatus(status);
40565
40609
  this.updateAllStatuses(status);
40566
40610
  }
40567
40611
  }