@powersync/web 0.0.0-dev-20251126195153 → 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);
@@ -40046,11 +40095,11 @@ __webpack_require__.r(__webpack_exports__);
40046
40095
  /* harmony export */ });
40047
40096
  /* harmony import */ var comlink__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! comlink */ "comlink");
40048
40097
  /* harmony import */ var comlink__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(comlink__WEBPACK_IMPORTED_MODULE_0__);
40049
- /* harmony import */ var _worker_sync_AbstractSharedSyncClientProvider__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../worker/sync/AbstractSharedSyncClientProvider */ "./lib/src/worker/sync/AbstractSharedSyncClientProvider.js");
40050
- /* harmony import */ var _worker_sync_SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../worker/sync/SharedSyncImplementation */ "./lib/src/worker/sync/SharedSyncImplementation.js");
40051
- /* harmony import */ var _adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../adapters/web-sql-flags */ "./lib/src/db/adapters/web-sql-flags.js");
40052
- /* harmony import */ var _WebStreamingSyncImplementation__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./WebStreamingSyncImplementation */ "./lib/src/db/sync/WebStreamingSyncImplementation.js");
40053
- /* harmony import */ var _shared_navigator__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../shared/navigator */ "./lib/src/shared/navigator.js");
40098
+ /* harmony import */ var _shared_navigator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../shared/navigator */ "./lib/src/shared/navigator.js");
40099
+ /* harmony import */ var _worker_sync_AbstractSharedSyncClientProvider__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../worker/sync/AbstractSharedSyncClientProvider */ "./lib/src/worker/sync/AbstractSharedSyncClientProvider.js");
40100
+ /* harmony import */ var _worker_sync_SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../worker/sync/SharedSyncImplementation */ "./lib/src/worker/sync/SharedSyncImplementation.js");
40101
+ /* harmony import */ var _adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../adapters/web-sql-flags */ "./lib/src/db/adapters/web-sql-flags.js");
40102
+ /* harmony import */ var _WebStreamingSyncImplementation__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./WebStreamingSyncImplementation */ "./lib/src/db/sync/WebStreamingSyncImplementation.js");
40054
40103
 
40055
40104
 
40056
40105
 
@@ -40061,7 +40110,7 @@ __webpack_require__.r(__webpack_exports__);
40061
40110
  * The shared worker will trigger methods on this side of the message port
40062
40111
  * via this client provider.
40063
40112
  */
40064
- class SharedSyncClientProvider extends _worker_sync_AbstractSharedSyncClientProvider__WEBPACK_IMPORTED_MODULE_1__.AbstractSharedSyncClientProvider {
40113
+ class SharedSyncClientProvider extends _worker_sync_AbstractSharedSyncClientProvider__WEBPACK_IMPORTED_MODULE_2__.AbstractSharedSyncClientProvider {
40065
40114
  options;
40066
40115
  statusChanged;
40067
40116
  webDB;
@@ -40132,7 +40181,7 @@ class SharedSyncClientProvider extends _worker_sync_AbstractSharedSyncClientProv
40132
40181
  /**
40133
40182
  * The local part of the sync implementation on the web, which talks to a sync implementation hosted in a shared worker.
40134
40183
  */
40135
- class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementation__WEBPACK_IMPORTED_MODULE_4__.WebStreamingSyncImplementation {
40184
+ class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementation__WEBPACK_IMPORTED_MODULE_5__.WebStreamingSyncImplementation {
40136
40185
  syncManager;
40137
40186
  clientProvider;
40138
40187
  messagePort;
@@ -40148,10 +40197,10 @@ class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementati
40148
40197
  */
40149
40198
  const resolvedWorkerOptions = {
40150
40199
  dbFilename: this.options.identifier,
40151
- temporaryStorage: _adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_3__.TemporaryStorageOption.MEMORY,
40152
- cacheSizeKb: _adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_3__.DEFAULT_CACHE_SIZE_KB,
40200
+ temporaryStorage: _adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_4__.TemporaryStorageOption.MEMORY,
40201
+ cacheSizeKb: _adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_4__.DEFAULT_CACHE_SIZE_KB,
40153
40202
  ...options,
40154
- flags: (0,_adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_3__.resolveWebSQLFlags)(options.flags)
40203
+ flags: (0,_adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_4__.resolveWebSQLFlags)(options.flags)
40155
40204
  };
40156
40205
  const syncWorker = options.sync?.worker;
40157
40206
  if (syncWorker) {
@@ -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,9 +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
  */
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
+ }
40268
+ // Awaiting here ensures the worker is waiting for the lock
40269
+ await this.syncManager.addLockBasedCloseSignal(lock.name);
40270
+ // The lock has been registered, we can continue with the initialization
40271
+ resolve();
40272
+ await new Promise((r) => {
40273
+ this.abortOnClose.signal.onabort = () => r();
40274
+ });
40275
+ });
40276
+ });
40184
40277
  const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
40185
40278
  const flags = { ...this.webOptions.flags, workers: undefined };
40186
- this.isInitialized = this.syncManager.setParams({
40279
+ await this.syncManager.setParams({
40187
40280
  dbParams: this.dbAdapter.getConfiguration(),
40188
40281
  streamOptions: {
40189
40282
  crudUploadThrottleMs,
@@ -40191,30 +40284,7 @@ class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementati
40191
40284
  retryDelayMs,
40192
40285
  flags: flags
40193
40286
  }
40194
- }, options.subscriptions);
40195
- /**
40196
- * Pass along any sync status updates to this listener
40197
- */
40198
- this.clientProvider = new SharedSyncClientProvider(this.webOptions, (status) => {
40199
- this.iterateListeners((l) => this.updateSyncStatus(status));
40200
- }, options.db);
40201
- /**
40202
- * The sync worker will call this client provider when it needs
40203
- * to fetch credentials or upload data.
40204
- * This performs bi-directional method calling.
40205
- */
40206
- comlink__WEBPACK_IMPORTED_MODULE_0__.expose(this.clientProvider, this.messagePort);
40207
- // Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
40208
- // will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
40209
- // to free resources associated with this tab.
40210
- (0,_shared_navigator__WEBPACK_IMPORTED_MODULE_5__.getNavigatorLocks)().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
40211
- if (!this.abortOnClose.signal.aborted) {
40212
- this.syncManager.addLockBasedCloseSignal(lock.name);
40213
- await new Promise((r) => {
40214
- this.abortOnClose.signal.onabort = () => r();
40215
- });
40216
- }
40217
- });
40287
+ }, this.options.subscriptions);
40218
40288
  }
40219
40289
  /**
40220
40290
  * Starts the sync process, this effectively acts as a call to
@@ -40242,13 +40312,13 @@ class SharedWebStreamingSyncImplementation extends _WebStreamingSyncImplementati
40242
40312
  // Listen for the close acknowledgment from the worker
40243
40313
  this.messagePort.addEventListener('message', (event) => {
40244
40314
  const payload = event.data;
40245
- if (payload?.event === _worker_sync_SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_2__.SharedSyncClientEvent.CLOSE_ACK) {
40315
+ if (payload?.event === _worker_sync_SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_3__.SharedSyncClientEvent.CLOSE_ACK) {
40246
40316
  resolve();
40247
40317
  }
40248
40318
  });
40249
40319
  // Signal the shared worker that this client is closing its connection to the worker
40250
40320
  const closeMessagePayload = {
40251
- event: _worker_sync_SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_2__.SharedSyncClientEvent.CLOSE_CLIENT,
40321
+ event: _worker_sync_SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_3__.SharedSyncClientEvent.CLOSE_CLIENT,
40252
40322
  data: {}
40253
40323
  };
40254
40324
  this.messagePort.postMessage(closeMessagePayload);
@@ -40772,7 +40842,6 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40772
40842
  statusListener;
40773
40843
  fetchCredentialsController;
40774
40844
  uploadDataController;
40775
- dbAdapter;
40776
40845
  syncParams;
40777
40846
  logger;
40778
40847
  lastConnectOptions;
@@ -40781,10 +40850,10 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40781
40850
  connectionManager;
40782
40851
  syncStatus;
40783
40852
  broadCastLogger;
40853
+ distributedDB;
40784
40854
  constructor() {
40785
40855
  super();
40786
40856
  this.ports = [];
40787
- this.dbAdapter = null;
40788
40857
  this.syncParams = null;
40789
40858
  this.logger = (0,_powersync_common__WEBPACK_IMPORTED_MODULE_0__.createLogger)('shared-sync');
40790
40859
  this.lastConnectOptions = undefined;
@@ -40797,26 +40866,23 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40797
40866
  }
40798
40867
  });
40799
40868
  });
40869
+ // Should be configured once we get params
40870
+ this.distributedDB = null;
40800
40871
  this.syncStatus = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SyncStatus({});
40801
40872
  this.broadCastLogger = new _BroadcastLogger__WEBPACK_IMPORTED_MODULE_7__.BroadcastLogger(this.ports);
40802
40873
  this.connectionManager = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.ConnectionManager({
40803
40874
  createSyncImplementation: async () => {
40804
- return this.portMutex.runExclusive(async () => {
40805
- await this.waitForReady();
40806
- if (!this.dbAdapter) {
40807
- 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());
40808
40880
  }
40809
- const sync = this.generateStreamingImplementation();
40810
- const onDispose = sync.registerListener({
40811
- statusChanged: (status) => {
40812
- this.updateAllStatuses(status.toJSON());
40813
- }
40814
- });
40815
- return {
40816
- sync,
40817
- onDispose
40818
- };
40819
40881
  });
40882
+ return {
40883
+ sync,
40884
+ onDispose
40885
+ };
40820
40886
  },
40821
40887
  logger: this.logger
40822
40888
  });
@@ -40827,6 +40893,18 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40827
40893
  get isConnected() {
40828
40894
  return this.connectionManager.syncStreamImplementation?.isConnected ?? false;
40829
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
+ }
40830
40908
  async waitForStatus(status) {
40831
40909
  return this.withSyncImplementation(async (sync) => {
40832
40910
  return sync.waitForStatus(status);
@@ -40869,10 +40947,6 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40869
40947
  this.collectActiveSubscriptions();
40870
40948
  if (this.syncParams) {
40871
40949
  // Cannot modify already existing sync implementation params
40872
- // But we can ask for a DB adapter, if required, at this point.
40873
- if (!this.dbAdapter) {
40874
- await this.openInternalDB();
40875
- }
40876
40950
  return;
40877
40951
  }
40878
40952
  // First time setting params
@@ -40880,13 +40954,28 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40880
40954
  if (params.streamOptions?.flags?.broadcastLogs) {
40881
40955
  this.logger = this.broadCastLogger;
40882
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
+ });
40883
40975
  self.onerror = (event) => {
40884
40976
  // Share any uncaught events on the broadcast logger
40885
40977
  this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
40886
40978
  };
40887
- if (!this.dbAdapter) {
40888
- await this.openInternalDB();
40889
- }
40890
40979
  this.iterateListeners((l) => l.initialized?.());
40891
40980
  });
40892
40981
  }
@@ -40917,7 +41006,9 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40917
41006
  port,
40918
41007
  clientProvider: comlink__WEBPACK_IMPORTED_MODULE_2__.wrap(port),
40919
41008
  currentSubscriptions: [],
40920
- closeListeners: []
41009
+ closeListeners: [],
41010
+ isProtectedFromClose: false,
41011
+ isClosing: false
40921
41012
  };
40922
41013
  this.ports.push(portProvider);
40923
41014
  // Give the newly connected client the latest status
@@ -40933,14 +41024,16 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40933
41024
  * clients.
40934
41025
  */
40935
41026
  async removePort(port) {
41027
+ // Ports might be removed faster than we can process them.
41028
+ port.isClosing = true;
40936
41029
  // Remove the port within a mutex context.
40937
41030
  // Warns if the port is not found. This should not happen in practice.
40938
41031
  // We return early if the port is not found.
40939
- const { trackedPort, shouldReconnect } = await this.portMutex.runExclusive(async () => {
41032
+ return await this.portMutex.runExclusive(async () => {
40940
41033
  const index = this.ports.findIndex((p) => p == port);
40941
41034
  if (index < 0) {
40942
41035
  this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`);
40943
- return {};
41036
+ return () => { };
40944
41037
  }
40945
41038
  const trackedPort = this.ports[index];
40946
41039
  // Remove from the list of active ports
@@ -40954,35 +41047,13 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
40954
41047
  abortController.controller.abort(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Closing pending requests after client port is removed'));
40955
41048
  }
40956
41049
  });
40957
- const shouldReconnect = !!this.connectionManager.syncStreamImplementation && this.ports.length > 0;
40958
- return {
40959
- shouldReconnect,
40960
- trackedPort
40961
- };
40962
- });
40963
- if (!trackedPort) {
40964
- // We could not find the port to remove
40965
- return () => { };
40966
- }
40967
- for (const closeListener of trackedPort.closeListeners) {
40968
- await closeListener();
40969
- }
40970
- if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
40971
- // Unconditionally close the connection because the database it's writing to has just been closed.
40972
- // The connection has been closed previously, this might throw. We should be able to ignore it.
40973
- await this.connectionManager
40974
- .disconnect()
40975
- .catch((ex) => this.logger.warn('Error while disconnecting. Will attempt to reconnect.', ex));
40976
- // Clearing the adapter will result in a new one being opened in connect
40977
- this.dbAdapter = null;
40978
- if (shouldReconnect) {
40979
- await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions ?? {});
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();
40980
41053
  }
40981
- }
40982
- // Re-index subscriptions, the subscriptions of the removed port would no longer be considered.
40983
- this.collectActiveSubscriptions();
40984
- // Release proxy
40985
- return () => trackedPort.clientProvider[comlink__WEBPACK_IMPORTED_MODULE_2__.releaseProxy]();
41054
+ this.collectActiveSubscriptions();
41055
+ return () => trackedPort.clientProvider[comlink__WEBPACK_IMPORTED_MODULE_2__.releaseProxy]();
41056
+ });
40986
41057
  }
40987
41058
  triggerCrudUpload() {
40988
41059
  this.withSyncImplementation(async (sync) => {
@@ -41019,10 +41090,13 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41019
41090
  const syncParams = this.syncParams;
41020
41091
  // Create a new StreamingSyncImplementation for each connect call. This is usually done is all SDKs.
41021
41092
  return new _db_sync_WebStreamingSyncImplementation__WEBPACK_IMPORTED_MODULE_4__.WebStreamingSyncImplementation({
41022
- 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),
41023
41094
  remote: new _db_sync_WebRemote__WEBPACK_IMPORTED_MODULE_3__.WebRemote({
41024
41095
  invalidateCredentials: async () => {
41025
- 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
+ }
41026
41100
  try {
41027
41101
  this.logger.log('calling the last port client provider to invalidate credentials');
41028
41102
  lastPort.clientProvider.invalidateCredentials();
@@ -41032,7 +41106,10 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41032
41106
  }
41033
41107
  },
41034
41108
  fetchCredentials: async () => {
41035
- 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
+ }
41036
41113
  return new Promise(async (resolve, reject) => {
41037
41114
  const abortController = new AbortController();
41038
41115
  this.fetchCredentialsController = {
@@ -41054,7 +41131,10 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41054
41131
  }
41055
41132
  }, this.logger),
41056
41133
  uploadCrud: async () => {
41057
- 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
+ }
41058
41138
  return new Promise(async (resolve, reject) => {
41059
41139
  const abortController = new AbortController();
41060
41140
  this.uploadDataController = {
@@ -41080,19 +41160,47 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41080
41160
  logger: this.logger
41081
41161
  });
41082
41162
  }
41163
+ /**
41164
+ * Opens a worker wrapped database connection. Using the last connected client port.
41165
+ */
41083
41166
  async openInternalDB() {
41084
- const lastClient = this.ports[this.ports.length - 1];
41085
- if (!lastClient) {
41086
- // Should not really happen in practice
41087
- throw new Error(`Could not open DB connection since no client is connected.`);
41088
- }
41089
- const workerPort = await lastClient.clientProvider.getDBWorkerPort();
41090
- const remote = comlink__WEBPACK_IMPORTED_MODULE_2__.wrap(workerPort);
41091
- const identifier = this.syncParams.dbParams.dbFilename;
41092
- const db = await remote(this.syncParams.dbParams);
41093
- const locked = new _db_adapters_LockedAsyncDatabaseAdapter__WEBPACK_IMPORTED_MODULE_5__.LockedAsyncDatabaseAdapter({
41094
- name: identifier,
41095
- 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
+ });
41096
41204
  const wrapped = new _db_adapters_WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_6__.WorkerWrappedAsyncDatabaseConnection({
41097
41205
  remote,
41098
41206
  baseConnection: db,
@@ -41103,15 +41211,21 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41103
41211
  });
41104
41212
  lastClient.closeListeners.push(async () => {
41105
41213
  this.logger.info('Aborting open connection because associated tab closed.');
41214
+ /**
41215
+ * Don't await this close operation. It might never resolve if the tab is closed.
41216
+ * We run the close operation first, before marking the remote as closed. This gives the database some chance
41217
+ * to close the connection.
41218
+ */
41106
41219
  wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
41107
41220
  wrapped.markRemoteClosed();
41108
41221
  });
41109
41222
  return wrapped;
41110
- },
41111
- logger: this.logger
41112
- });
41113
- await locked.init();
41114
- 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
+ }
41115
41229
  }
41116
41230
  /**
41117
41231
  * A method to update the all shared statuses for each
@@ -41134,6 +41248,29 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
41134
41248
  this.updateAllStatuses(status);
41135
41249
  }
41136
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
+ }
41137
41274
 
41138
41275
 
41139
41276
  /***/ }),
@@ -41490,6 +41627,7 @@ __webpack_require__.r(__webpack_exports__);
41490
41627
  /* harmony export */ AbstractWebPowerSyncDatabaseOpenFactory: () => (/* reexport safe */ _db_adapters_AbstractWebPowerSyncDatabaseOpenFactory__WEBPACK_IMPORTED_MODULE_2__.AbstractWebPowerSyncDatabaseOpenFactory),
41491
41628
  /* harmony export */ AbstractWebSQLOpenFactory: () => (/* reexport safe */ _db_adapters_AbstractWebSQLOpenFactory__WEBPACK_IMPORTED_MODULE_3__.AbstractWebSQLOpenFactory),
41492
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),
41493
41631
  /* harmony export */ DEFAULT_CACHE_SIZE_KB: () => (/* reexport safe */ _db_adapters_web_sql_flags__WEBPACK_IMPORTED_MODULE_8__.DEFAULT_CACHE_SIZE_KB),
41494
41632
  /* harmony export */ DEFAULT_MODULE_FACTORIES: () => (/* reexport safe */ _db_adapters_wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__.DEFAULT_MODULE_FACTORIES),
41495
41633
  /* harmony export */ DEFAULT_POWERSYNC_FLAGS: () => (/* reexport safe */ _db_PowerSyncDatabase__WEBPACK_IMPORTED_MODULE_9__.DEFAULT_POWERSYNC_FLAGS),