@powersync/web 0.0.0-dev-20251127205344 → 0.0.0-dev-20251129133952

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
@@ -38680,7 +38680,15 @@ class AbstractWebSQLOpenFactory {
38680
38680
 
38681
38681
  "use strict";
38682
38682
  __webpack_require__.r(__webpack_exports__);
38683
-
38683
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
38684
+ /* harmony export */ ConnectionClosedError: () => (/* binding */ ConnectionClosedError)
38685
+ /* harmony export */ });
38686
+ class ConnectionClosedError extends Error {
38687
+ constructor(message) {
38688
+ super(message);
38689
+ this.name = 'ConnectionClosedError';
38690
+ }
38691
+ }
38684
38692
 
38685
38693
 
38686
38694
  /***/ }),
@@ -38699,8 +38707,10 @@ __webpack_require__.r(__webpack_exports__);
38699
38707
  /* harmony import */ var _powersync_common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @powersync/common */ "@powersync/common");
38700
38708
  /* harmony import */ var _powersync_common__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_powersync_common__WEBPACK_IMPORTED_MODULE_0__);
38701
38709
  /* harmony import */ var _shared_navigator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../..//shared/navigator */ "./lib/src/shared/navigator.js");
38702
- /* harmony import */ var _WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./WorkerWrappedAsyncDatabaseConnection */ "./lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js");
38703
- /* harmony import */ var _wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./wa-sqlite/WASQLiteConnection */ "./lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js");
38710
+ /* harmony import */ var _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./AsyncDatabaseConnection */ "./lib/src/db/adapters/AsyncDatabaseConnection.js");
38711
+ /* harmony import */ var _WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./WorkerWrappedAsyncDatabaseConnection */ "./lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js");
38712
+ /* harmony import */ var _wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./wa-sqlite/WASQLiteConnection */ "./lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js");
38713
+
38704
38714
 
38705
38715
 
38706
38716
 
@@ -38722,6 +38732,8 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
38722
38732
  _config = null;
38723
38733
  pendingAbortControllers;
38724
38734
  requiresHolds;
38735
+ requiresReOpen;
38736
+ databaseOpenPromise = null;
38725
38737
  closing;
38726
38738
  closed;
38727
38739
  constructor(options) {
@@ -38733,6 +38745,7 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
38733
38745
  this.closed = false;
38734
38746
  this.closing = false;
38735
38747
  this.requiresHolds = null;
38748
+ this.requiresReOpen = false;
38736
38749
  // Set the name if provided. We can query for the name if not available yet
38737
38750
  this.debugMode = options.debugMode ?? false;
38738
38751
  if (this.debugMode) {
@@ -38771,16 +38784,26 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
38771
38784
  async init() {
38772
38785
  return this.initPromise;
38773
38786
  }
38774
- async _init() {
38787
+ async openInternalDB() {
38788
+ // Dispose any previous table change listener.
38789
+ this._disposeTableChangeListener?.();
38790
+ this._disposeTableChangeListener = null;
38791
+ const isReOpen = !!this._db;
38775
38792
  this._db = await this.options.openConnection();
38776
38793
  await this._db.init();
38777
38794
  this._config = await this._db.getConfig();
38778
38795
  await this.registerOnChangeListener(this._db);
38779
- this.iterateListeners((cb) => cb.initialized?.());
38796
+ if (isReOpen) {
38797
+ this.iterateListeners((cb) => cb.databaseReOpened?.());
38798
+ }
38780
38799
  /**
38781
38800
  * This is only required for the long-lived shared IndexedDB connections.
38782
38801
  */
38783
- this.requiresHolds = this._config.vfs == _wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_3__.WASQLiteVFS.IDBBatchAtomicVFS;
38802
+ this.requiresHolds = this._config.vfs == _wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__.WASQLiteVFS.IDBBatchAtomicVFS;
38803
+ }
38804
+ async _init() {
38805
+ await this.openInternalDB();
38806
+ this.iterateListeners((cb) => cb.initialized?.());
38784
38807
  }
38785
38808
  getConfiguration() {
38786
38809
  if (!this._config) {
@@ -38793,7 +38816,7 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
38793
38816
  await this.initPromise;
38794
38817
  }
38795
38818
  async shareConnection() {
38796
- if (false == this._db instanceof _WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__.WorkerWrappedAsyncDatabaseConnection) {
38819
+ if (false == this._db instanceof _WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_3__.WorkerWrappedAsyncDatabaseConnection) {
38797
38820
  throw new Error(`Only worker connections can be shared`);
38798
38821
  }
38799
38822
  return this._db.shareConnection();
@@ -38847,13 +38870,13 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
38847
38870
  async readLock(fn, options) {
38848
38871
  await this.waitForInitialized();
38849
38872
  return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
38850
- timeoutMs: options?.timeoutMs
38873
+ timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
38851
38874
  });
38852
38875
  }
38853
38876
  async writeLock(fn, options) {
38854
38877
  await this.waitForInitialized();
38855
38878
  return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
38856
- timeoutMs: options?.timeoutMs
38879
+ timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
38857
38880
  });
38858
38881
  }
38859
38882
  async acquireLock(callback, options) {
@@ -38864,7 +38887,7 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
38864
38887
  const abortController = new AbortController();
38865
38888
  this.pendingAbortControllers.add(abortController);
38866
38889
  const { timeoutMs } = options ?? {};
38867
- const timoutId = timeoutMs
38890
+ const timeoutId = timeoutMs
38868
38891
  ? setTimeout(() => {
38869
38892
  abortController.abort(`Timeout after ${timeoutMs}ms`);
38870
38893
  this.pendingAbortControllers.delete(abortController);
@@ -38872,13 +38895,37 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
38872
38895
  : null;
38873
38896
  return (0,_shared_navigator__WEBPACK_IMPORTED_MODULE_1__.getNavigatorLocks)().request(`db-lock-${this._dbIdentifier}`, { signal: abortController.signal }, async () => {
38874
38897
  this.pendingAbortControllers.delete(abortController);
38875
- if (timoutId) {
38876
- clearTimeout(timoutId);
38898
+ if (timeoutId) {
38899
+ clearTimeout(timeoutId);
38877
38900
  }
38878
- const holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
38901
+ let holdId = null;
38879
38902
  try {
38903
+ // The database is being opened in the background. Wait for it here.
38904
+ if (this.databaseOpenPromise) {
38905
+ try {
38906
+ await this.databaseOpenPromise;
38907
+ }
38908
+ catch (ex) {
38909
+ // This will cause a retry of opening the database.
38910
+ const wrappedError = new _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__.ConnectionClosedError('Could not open database');
38911
+ wrappedError.cause = ex;
38912
+ throw wrappedError;
38913
+ }
38914
+ }
38915
+ holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
38880
38916
  return await callback();
38881
38917
  }
38918
+ catch (ex) {
38919
+ if (ex instanceof _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__.ConnectionClosedError) {
38920
+ if (this.options.reOpenOnConnectionClosed && !this.databaseOpenPromise && !this.closing) {
38921
+ // Immediately re-open the database. We need to miss as little table updates as possible.
38922
+ this.databaseOpenPromise = this.openInternalDB().finally(() => {
38923
+ this.databaseOpenPromise = null;
38924
+ });
38925
+ }
38926
+ }
38927
+ throw ex;
38928
+ }
38882
38929
  finally {
38883
38930
  if (holdId) {
38884
38931
  await this.baseDB.releaseHold(holdId);
@@ -39102,6 +39149,8 @@ __webpack_require__.r(__webpack_exports__);
39102
39149
  /* harmony export */ });
39103
39150
  /* harmony import */ var comlink__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! comlink */ "comlink");
39104
39151
  /* harmony import */ var comlink__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(comlink__WEBPACK_IMPORTED_MODULE_0__);
39152
+ /* harmony import */ var _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./AsyncDatabaseConnection */ "./lib/src/db/adapters/AsyncDatabaseConnection.js");
39153
+
39105
39154
 
39106
39155
  /**
39107
39156
  * Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
@@ -39149,12 +39198,12 @@ class WorkerWrappedAsyncDatabaseConnection {
39149
39198
  if (controller) {
39150
39199
  return new Promise((resolve, reject) => {
39151
39200
  if (controller.signal.aborted) {
39152
- reject(new Error('Called operation on closed remote'));
39201
+ reject(new _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_1__.ConnectionClosedError('Called operation on closed remote'));
39153
39202
  // Don't run the operation if we're going to reject
39154
39203
  return;
39155
39204
  }
39156
39205
  function handleAbort() {
39157
- reject(new Error('Remote peer closed with request in flight'));
39206
+ reject(new _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_1__.ConnectionClosedError('Remote peer closed with request in flight'));
39158
39207
  }
39159
39208
  function completePromise(action) {
39160
39209
  controller.signal.removeEventListener('abort', handleAbort);
@@ -40172,7 +40221,19 @@ class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementati
40172
40221
  type: undefined
40173
40222
  }).port;
40174
40223
  }
40224
+ /**
40225
+ * Pass along any sync status updates to this listener
40226
+ */
40227
+ this.clientProvider = new SharedSyncClientProvider(this.webOptions, (status) => {
40228
+ this.updateSyncStatus(status);
40229
+ }, options.db);
40175
40230
  this.syncManager = comlink__WEBPACK_IMPORTED_MODULE_0__.wrap(this.messagePort);
40231
+ /**
40232
+ * The sync worker will call this client provider when it needs
40233
+ * to fetch credentials or upload data.
40234
+ * This performs bi-directional method calling.
40235
+ */
40236
+ comlink__WEBPACK_IMPORTED_MODULE_0__.expose(this.clientProvider, this.messagePort);
40176
40237
  this.syncManager.setLogLevel(this.logger.getLevel());
40177
40238
  this.triggerCrudUpload = this.syncManager.triggerCrudUpload;
40178
40239
  /**
@@ -40181,22 +40242,41 @@ class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementati
40181
40242
  * DB worker, but a port to the DB worker can be transferred to the
40182
40243
  * sync worker.
40183
40244
  */
40184
- const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
40185
- const flags = { ...this.webOptions.flags, workers: undefined };
40186
- // Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
40187
- // will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
40188
- // to free resources associated with this tab.
40189
- // We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
40190
- (0,_shared_navigator__WEBPACK_IMPORTED_MODULE_1__.getNavigatorLocks)().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
40191
- if (!this.abortOnClose.signal.aborted) {
40245
+ this.isInitialized = this._init();
40246
+ }
40247
+ async _init() {
40248
+ /**
40249
+ * The general flow of initialization is:
40250
+ * - The client requests a unique navigator lock.
40251
+ * - Once the lock is acquired, we register the lock with the shared worker.
40252
+ * - The shared worker can then request the same lock. The client has been closed if the shared worker can acquire the lock.
40253
+ * - Once the shared worker knows the client's lock, we can guarentee that the shared worker will detect if the client has been closed.
40254
+ * - This makes the client safe for the shared worker to use.
40255
+ * - The client side lock is held until the client is disposed.
40256
+ * - We resolve the top-level promise after the lock has been registered with the shared worker.
40257
+ * - The client sends the params to the shared worker after locks have been registered.
40258
+ */
40259
+ await new Promise((resolve) => {
40260
+ // Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
40261
+ // will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
40262
+ // to free resources associated with this tab.
40263
+ // We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
40264
+ (0,_shared_navigator__WEBPACK_IMPORTED_MODULE_1__.getNavigatorLocks)().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
40265
+ if (this.abortOnClose.signal.aborted) {
40266
+ return;
40267
+ }
40192
40268
  // Awaiting here ensures the worker is waiting for the lock
40193
40269
  await this.syncManager.addLockBasedCloseSignal(lock.name);
40270
+ // The lock has been registered, we can continue with the initialization
40271
+ resolve();
40194
40272
  await new Promise((r) => {
40195
40273
  this.abortOnClose.signal.onabort = () => r();
40196
40274
  });
40197
- }
40275
+ });
40198
40276
  });
40199
- this.isInitialized = this.syncManager.setParams({
40277
+ const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
40278
+ const flags = { ...this.webOptions.flags, workers: undefined };
40279
+ await this.syncManager.setParams({
40200
40280
  dbParams: this.dbAdapter.getConfiguration(),
40201
40281
  streamOptions: {
40202
40282
  crudUploadThrottleMs,
@@ -40204,19 +40284,7 @@ class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementati
40204
40284
  retryDelayMs,
40205
40285
  flags: flags
40206
40286
  }
40207
- }, options.subscriptions);
40208
- /**
40209
- * Pass along any sync status updates to this listener
40210
- */
40211
- this.clientProvider = new SharedSyncClientProvider(this.webOptions, (status) => {
40212
- this.iterateListeners((l) => this.updateSyncStatus(status));
40213
- }, options.db);
40214
- /**
40215
- * The sync worker will call this client provider when it needs
40216
- * to fetch credentials or upload data.
40217
- * This performs bi-directional method calling.
40218
- */
40219
- comlink__WEBPACK_IMPORTED_MODULE_0__.expose(this.clientProvider, this.messagePort);
40287
+ }, this.options.subscriptions);
40220
40288
  }
40221
40289
  /**
40222
40290
  * Starts the sync process, this effectively acts as a call to
@@ -40774,7 +40842,6 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40774
40842
  statusListener;
40775
40843
  fetchCredentialsController;
40776
40844
  uploadDataController;
40777
- dbAdapter;
40778
40845
  syncParams;
40779
40846
  logger;
40780
40847
  lastConnectOptions;
@@ -40783,10 +40850,10 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40783
40850
  connectionManager;
40784
40851
  syncStatus;
40785
40852
  broadCastLogger;
40853
+ distributedDB;
40786
40854
  constructor() {
40787
40855
  super();
40788
40856
  this.ports = [];
40789
- this.dbAdapter = null;
40790
40857
  this.syncParams = null;
40791
40858
  this.logger = (0,_powersync_common__WEBPACK_IMPORTED_MODULE_0__.createLogger)('shared-sync');
40792
40859
  this.lastConnectOptions = undefined;
@@ -40799,26 +40866,23 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40799
40866
  }
40800
40867
  });
40801
40868
  });
40869
+ // Should be configured once we get params
40870
+ this.distributedDB = null;
40802
40871
  this.syncStatus = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SyncStatus({});
40803
40872
  this.broadCastLogger = new _BroadcastLogger__WEBPACK_IMPORTED_MODULE_7__.BroadcastLogger(this.ports);
40804
40873
  this.connectionManager = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.ConnectionManager({
40805
40874
  createSyncImplementation: async () => {
40806
- return this.portMutex.runExclusive(async () => {
40807
- await this.waitForReady();
40808
- if (!this.dbAdapter) {
40809
- await this.openInternalDB();
40875
+ await this.waitForReady();
40876
+ const sync = this.generateStreamingImplementation();
40877
+ const onDispose = sync.registerListener({
40878
+ statusChanged: (status) => {
40879
+ this.updateAllStatuses(status.toJSON());
40810
40880
  }
40811
- const sync = this.generateStreamingImplementation();
40812
- const onDispose = sync.registerListener({
40813
- statusChanged: (status) => {
40814
- this.updateAllStatuses(status.toJSON());
40815
- }
40816
- });
40817
- return {
40818
- sync,
40819
- onDispose
40820
- };
40821
40881
  });
40882
+ return {
40883
+ sync,
40884
+ onDispose
40885
+ };
40822
40886
  },
40823
40887
  logger: this.logger
40824
40888
  });
@@ -40829,6 +40893,18 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40829
40893
  get isConnected() {
40830
40894
  return this.connectionManager.syncStreamImplementation?.isConnected ?? false;
40831
40895
  }
40896
+ /**
40897
+ * Gets the last client port which we know is safe from unexpected closes.
40898
+ */
40899
+ get lastWrappedPort() {
40900
+ // Find the last port which is protected from close
40901
+ for (let i = this.ports.length - 1; i >= 0; i--) {
40902
+ if (this.ports[i].isProtectedFromClose && !this.ports[i].isClosing) {
40903
+ return this.ports[i];
40904
+ }
40905
+ }
40906
+ return;
40907
+ }
40832
40908
  async waitForStatus(status) {
40833
40909
  return this.withSyncImplementation(async (sync) => {
40834
40910
  return sync.waitForStatus(status);
@@ -40871,10 +40947,6 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40871
40947
  this.collectActiveSubscriptions();
40872
40948
  if (this.syncParams) {
40873
40949
  // Cannot modify already existing sync implementation params
40874
- // But we can ask for a DB adapter, if required, at this point.
40875
- if (!this.dbAdapter) {
40876
- await this.openInternalDB();
40877
- }
40878
40950
  return;
40879
40951
  }
40880
40952
  // First time setting params
@@ -40882,13 +40954,28 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40882
40954
  if (params.streamOptions?.flags?.broadcastLogs) {
40883
40955
  this.logger = this.broadCastLogger;
40884
40956
  }
40957
+ const lockedAdapter = new _db_adapters_LockedAsyncDatabaseAdapter__WEBPACK_IMPORTED_MODULE_5__.LockedAsyncDatabaseAdapter({
40958
+ name: params.dbParams.dbFilename,
40959
+ openConnection: async () => {
40960
+ // Gets a connection from the clients when a new connection is requested.
40961
+ return await this.openInternalDB();
40962
+ },
40963
+ logger: this.logger,
40964
+ reOpenOnConnectionClosed: true
40965
+ });
40966
+ this.distributedDB = lockedAdapter;
40967
+ await lockedAdapter.init();
40968
+ lockedAdapter.registerListener({
40969
+ databaseReOpened: () => {
40970
+ // We may have missed some table updates while the database was closed.
40971
+ // We can poke the crud in case we missed any updates.
40972
+ this.connectionManager.syncStreamImplementation?.triggerCrudUpload();
40973
+ }
40974
+ });
40885
40975
  self.onerror = (event) => {
40886
40976
  // Share any uncaught events on the broadcast logger
40887
40977
  this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
40888
40978
  };
40889
- if (!this.dbAdapter) {
40890
- await this.openInternalDB();
40891
- }
40892
40979
  this.iterateListeners((l) => l.initialized?.());
40893
40980
  });
40894
40981
  }
@@ -40919,7 +41006,9 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40919
41006
  port,
40920
41007
  clientProvider: comlink__WEBPACK_IMPORTED_MODULE_2__.wrap(port),
40921
41008
  currentSubscriptions: [],
40922
- closeListeners: []
41009
+ closeListeners: [],
41010
+ isProtectedFromClose: false,
41011
+ isClosing: false
40923
41012
  };
40924
41013
  this.ports.push(portProvider);
40925
41014
  // Give the newly connected client the latest status
@@ -40935,14 +41024,16 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40935
41024
  * clients.
40936
41025
  */
40937
41026
  async removePort(port) {
41027
+ // Ports might be removed faster than we can process them.
41028
+ port.isClosing = true;
40938
41029
  // Remove the port within a mutex context.
40939
41030
  // Warns if the port is not found. This should not happen in practice.
40940
41031
  // We return early if the port is not found.
40941
- const { trackedPort, shouldReconnect } = await this.portMutex.runExclusive(async () => {
41032
+ return await this.portMutex.runExclusive(async () => {
40942
41033
  const index = this.ports.findIndex((p) => p == port);
40943
41034
  if (index < 0) {
40944
41035
  this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`);
40945
- return {};
41036
+ return () => { };
40946
41037
  }
40947
41038
  const trackedPort = this.ports[index];
40948
41039
  // Remove from the list of active ports
@@ -40956,40 +41047,13 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40956
41047
  abortController.controller.abort(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Closing pending requests after client port is removed'));
40957
41048
  }
40958
41049
  });
40959
- const shouldReconnect = !!this.connectionManager.syncStreamImplementation && this.ports.length > 0;
40960
- /**
40961
- * If the current database adapter is the one that is being closed, we need to disconnect from the backend.
40962
- * We can disconnect in the portMutex lock. This ensures the disconnect is not affected by potential other
40963
- * connect operations coming from other tabs.
40964
- */
40965
- if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
40966
- this.logger.debug(`Disconnecting due to closed database: should reconnect: ${shouldReconnect}`);
40967
- this.dbAdapter = null;
40968
- // Unconditionally close the connection because the database it's writing to has just been closed.
40969
- // The connection has been closed previously, this might throw. We should be able to ignore it.
40970
- await this.connectionManager
40971
- .disconnect()
40972
- .catch((ex) => this.logger.warn('Error while disconnecting. Will attempt to reconnect.', ex));
41050
+ // Close the worker wrapped database connection, we can't accurately rely on this connection
41051
+ for (const closeListener of trackedPort.closeListeners) {
41052
+ await closeListener();
40973
41053
  }
40974
- return {
40975
- shouldReconnect,
40976
- trackedPort
40977
- };
41054
+ this.collectActiveSubscriptions();
41055
+ return () => trackedPort.clientProvider[comlink__WEBPACK_IMPORTED_MODULE_2__.releaseProxy]();
40978
41056
  });
40979
- if (!trackedPort) {
40980
- // We could not find the port to remove
40981
- return () => { };
40982
- }
40983
- for (const closeListener of trackedPort.closeListeners) {
40984
- await closeListener();
40985
- }
40986
- if (shouldReconnect) {
40987
- await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions ?? {});
40988
- }
40989
- // Re-index subscriptions, the subscriptions of the removed port would no longer be considered.
40990
- this.collectActiveSubscriptions();
40991
- // Release proxy
40992
- return () => trackedPort.clientProvider[comlink__WEBPACK_IMPORTED_MODULE_2__.releaseProxy]();
40993
41057
  }
40994
41058
  triggerCrudUpload() {
40995
41059
  this.withSyncImplementation(async (sync) => {
@@ -41026,10 +41090,13 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41026
41090
  const syncParams = this.syncParams;
41027
41091
  // Create a new StreamingSyncImplementation for each connect call. This is usually done is all SDKs.
41028
41092
  return new _db_sync_WebStreamingSyncImplementation__WEBPACK_IMPORTED_MODULE_4__.WebStreamingSyncImplementation({
41029
- adapter: new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SqliteBucketStorage(this.dbAdapter, this.logger),
41093
+ adapter: new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SqliteBucketStorage(this.distributedDB, this.logger),
41030
41094
  remote: new _db_sync_WebRemote__WEBPACK_IMPORTED_MODULE_3__.WebRemote({
41031
41095
  invalidateCredentials: async () => {
41032
- const lastPort = this.ports[this.ports.length - 1];
41096
+ const lastPort = this.lastWrappedPort;
41097
+ if (!lastPort) {
41098
+ throw new Error('No client port found to invalidate credentials');
41099
+ }
41033
41100
  try {
41034
41101
  this.logger.log('calling the last port client provider to invalidate credentials');
41035
41102
  lastPort.clientProvider.invalidateCredentials();
@@ -41039,7 +41106,10 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41039
41106
  }
41040
41107
  },
41041
41108
  fetchCredentials: async () => {
41042
- const lastPort = this.ports[this.ports.length - 1];
41109
+ const lastPort = this.lastWrappedPort;
41110
+ if (!lastPort) {
41111
+ throw new Error('No client port found to fetch credentials');
41112
+ }
41043
41113
  return new Promise(async (resolve, reject) => {
41044
41114
  const abortController = new AbortController();
41045
41115
  this.fetchCredentialsController = {
@@ -41061,7 +41131,10 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41061
41131
  }
41062
41132
  }, this.logger),
41063
41133
  uploadCrud: async () => {
41064
- const lastPort = this.ports[this.ports.length - 1];
41134
+ const lastPort = this.lastWrappedPort;
41135
+ if (!lastPort) {
41136
+ throw new Error('No client port found to upload crud');
41137
+ }
41065
41138
  return new Promise(async (resolve, reject) => {
41066
41139
  const abortController = new AbortController();
41067
41140
  this.uploadDataController = {
@@ -41087,19 +41160,47 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41087
41160
  logger: this.logger
41088
41161
  });
41089
41162
  }
41163
+ /**
41164
+ * Opens a worker wrapped database connection. Using the last connected client port.
41165
+ */
41090
41166
  async openInternalDB() {
41091
- const lastClient = this.ports[this.ports.length - 1];
41092
- if (!lastClient) {
41093
- // Should not really happen in practice
41094
- throw new Error(`Could not open DB connection since no client is connected.`);
41095
- }
41096
- const workerPort = await lastClient.clientProvider.getDBWorkerPort();
41097
- const remote = comlink__WEBPACK_IMPORTED_MODULE_2__.wrap(workerPort);
41098
- const identifier = this.syncParams.dbParams.dbFilename;
41099
- const db = await remote(this.syncParams.dbParams);
41100
- const locked = new _db_adapters_LockedAsyncDatabaseAdapter__WEBPACK_IMPORTED_MODULE_5__.LockedAsyncDatabaseAdapter({
41101
- name: identifier,
41102
- openConnection: async () => {
41167
+ while (true) {
41168
+ try {
41169
+ const lastClient = this.lastWrappedPort;
41170
+ if (!lastClient) {
41171
+ // Should not really happen in practice
41172
+ throw new Error(`Could not open DB connection since no client is connected.`);
41173
+ }
41174
+ /**
41175
+ * Handle cases where the client might close while opening a connection.
41176
+ */
41177
+ const abortController = new AbortController();
41178
+ const closeListener = () => {
41179
+ abortController.abort();
41180
+ };
41181
+ const removeCloseListener = () => {
41182
+ const index = lastClient.closeListeners.indexOf(closeListener);
41183
+ if (index >= 0) {
41184
+ lastClient.closeListeners.splice(index, 1);
41185
+ }
41186
+ };
41187
+ lastClient.closeListeners.push(closeListener);
41188
+ const workerPort = await withAbort(() => lastClient.clientProvider.getDBWorkerPort(), abortController.signal).catch((ex) => {
41189
+ removeCloseListener();
41190
+ throw ex;
41191
+ });
41192
+ const remote = comlink__WEBPACK_IMPORTED_MODULE_2__.wrap(workerPort);
41193
+ const identifier = this.syncParams.dbParams.dbFilename;
41194
+ /**
41195
+ * The open could fail if the tab is closed while we're busy opening the database.
41196
+ * This operation is typically executed inside an exclusive portMutex lock.
41197
+ * We typically execute the closeListeners using the portMutex in a different context.
41198
+ * We can't rely on the closeListeners to abort the operation if the tab is closed.
41199
+ */
41200
+ const db = await withAbort(() => remote(this.syncParams.dbParams), abortController.signal).finally(() => {
41201
+ // We can remove the close listener here since we no longer need it past this point.
41202
+ removeCloseListener();
41203
+ });
41103
41204
  const wrapped = new _db_adapters_WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_6__.WorkerWrappedAsyncDatabaseConnection({
41104
41205
  remote,
41105
41206
  baseConnection: db,
@@ -41119,11 +41220,12 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41119
41220
  wrapped.markRemoteClosed();
41120
41221
  });
41121
41222
  return wrapped;
41122
- },
41123
- logger: this.logger
41124
- });
41125
- await locked.init();
41126
- this.dbAdapter = lastClient.db = locked;
41223
+ }
41224
+ catch (ex) {
41225
+ this.logger.warn('Error opening internal DB', ex);
41226
+ await new Promise((resolve) => setTimeout(resolve, 1000));
41227
+ }
41228
+ }
41127
41229
  }
41128
41230
  /**
41129
41231
  * A method to update the all shared statuses for each
@@ -41146,6 +41248,29 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41146
41248
  this.updateAllStatuses(status);
41147
41249
  }
41148
41250
  }
41251
+ /**
41252
+ * Runs the action with an abort controller.
41253
+ */
41254
+ function withAbort(action, signal) {
41255
+ return new Promise((resolve, reject) => {
41256
+ if (signal.aborted) {
41257
+ reject(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Operation aborted by abort controller'));
41258
+ return;
41259
+ }
41260
+ function handleAbort() {
41261
+ signal.removeEventListener('abort', handleAbort);
41262
+ reject(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Operation aborted by abort controller'));
41263
+ }
41264
+ signal.addEventListener('abort', handleAbort, { once: true });
41265
+ function completePromise(action) {
41266
+ signal.removeEventListener('abort', handleAbort);
41267
+ action();
41268
+ }
41269
+ action()
41270
+ .then((data) => completePromise(() => resolve(data)))
41271
+ .catch((e) => completePromise(() => reject(e)));
41272
+ });
41273
+ }
41149
41274
 
41150
41275
 
41151
41276
  /***/ }),
@@ -41502,6 +41627,7 @@ __webpack_require__.r(__webpack_exports__);
41502
41627
  /* harmony export */ AbstractWebPowerSyncDatabaseOpenFactory: () => (/* reexport safe */ _db_adapters_AbstractWebPowerSyncDatabaseOpenFactory__WEBPACK_IMPORTED_MODULE_2__.AbstractWebPowerSyncDatabaseOpenFactory),
41503
41628
  /* harmony export */ AbstractWebSQLOpenFactory: () => (/* reexport safe */ _db_adapters_AbstractWebSQLOpenFactory__WEBPACK_IMPORTED_MODULE_3__.AbstractWebSQLOpenFactory),
41504
41629
  /* harmony export */ AsyncWASQLiteModuleFactory: () => (/* reexport safe */ _db_adapters_wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__.AsyncWASQLiteModuleFactory),
41630
+ /* harmony export */ ConnectionClosedError: () => (/* reexport safe */ _db_adapters_AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_1__.ConnectionClosedError),
41505
41631
  /* harmony export */ DEFAULT_CACHE_SIZE_KB: () => (/* reexport safe */ _db_adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_8__.DEFAULT_CACHE_SIZE_KB),
41506
41632
  /* harmony export */ DEFAULT_MODULE_FACTORIES: () => (/* reexport safe */ _db_adapters_wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__.DEFAULT_MODULE_FACTORIES),
41507
41633
  /* harmony export */ DEFAULT_POWERSYNC_FLAGS: () => (/* reexport safe */ _db_PowerSyncDatabase__WEBPACK_IMPORTED_MODULE_9__.DEFAULT_POWERSYNC_FLAGS),