@powersync/web 0.0.0-dev-20251201150812 → 0.0.0-dev-20251209082930

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 (47) hide show
  1. package/dist/0b19af1befc07ce338dd.wasm +0 -0
  2. package/dist/2632c3bda9473da74fd5.wasm +0 -0
  3. package/dist/64f5351ba3784bfe2f3e.wasm +0 -0
  4. package/dist/9318ca94aac4d0fe0135.wasm +0 -0
  5. package/dist/index.umd.js +219 -115
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +164 -109
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +24 -12
  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 +16 -3
  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 +16 -3
  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 +16 -3
  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 +16 -3
  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_OPFSCoopSyncVFS_js.umd.js +18 -11
  20. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js.map +1 -1
  21. package/lib/package.json +2 -2
  22. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +4 -1
  23. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +60 -29
  24. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +11 -2
  25. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +1 -1
  26. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +2 -2
  27. package/lib/src/worker/sync/SharedSyncImplementation.js +80 -68
  28. package/lib/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +5 -5
  30. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +79 -48
  31. package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +11 -3
  32. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +3 -3
  33. package/src/worker/db/WASQLiteDB.worker.ts +0 -1
  34. package/src/worker/sync/SharedSyncImplementation.ts +89 -74
  35. package/dist/1807036ae51c10ee4d23.wasm +0 -0
  36. package/dist/307d8ce2280e3bae09d5.wasm +0 -0
  37. package/dist/cd8b9e8f4c87bf81c169.wasm +0 -0
  38. package/dist/e797080f5ed0b5324166.wasm +0 -0
  39. package/lib/src/worker/sync/MockSyncService.d.ts +0 -2
  40. package/lib/src/worker/sync/MockSyncService.js +0 -3
  41. package/lib/src/worker/sync/MockSyncServiceTypes.d.ts +0 -101
  42. package/lib/src/worker/sync/MockSyncServiceTypes.js +0 -1
  43. package/lib/src/worker/sync/MockSyncServiceWorker.d.ts +0 -56
  44. package/lib/src/worker/sync/MockSyncServiceWorker.js +0 -369
  45. package/src/worker/sync/MockSyncService.ts +0 -3
  46. package/src/worker/sync/MockSyncServiceTypes.ts +0 -71
  47. package/src/worker/sync/MockSyncServiceWorker.ts +0 -406
@@ -1147,8 +1147,11 @@ function Factory(Module) {
1147
1147
  // Wait for all pending retry operations to complete. This is
1148
1148
  // normally empty on the first loop iteration.
1149
1149
  if (Module.retryOps.length) {
1150
- await Promise.all(Module.retryOps);
1151
- Module.retryOps = [];
1150
+ try {
1151
+ await Promise.all(Module.retryOps);
1152
+ } finally {
1153
+ Module.retryOps = [];
1154
+ }
1152
1155
  }
1153
1156
 
1154
1157
  rc = await f();
@@ -14673,7 +14676,7 @@ The next upload iteration will be delayed.`);
14673
14676
  uploadError: ex
14674
14677
  }
14675
14678
  });
14676
- await this.delayRetry(controller.signal, this.options.crudUploadThrottleMs);
14679
+ await this.delayRetry(controller.signal);
14677
14680
  if (!this.isConnected) {
14678
14681
  // Exit the upload loop if the sync stream is no longer connected
14679
14682
  break;
@@ -15384,14 +15387,14 @@ The next upload iteration will be delayed.`);
15384
15387
  // trigger this for all updates
15385
15388
  this.iterateListeners((cb) => cb.statusUpdated?.(options));
15386
15389
  }
15387
- async delayRetry(signal, delayMs) {
15390
+ async delayRetry(signal) {
15388
15391
  return new Promise((resolve) => {
15389
15392
  if (signal?.aborted) {
15390
15393
  // If the signal is already aborted, resolve immediately
15391
15394
  resolve();
15392
15395
  return;
15393
15396
  }
15394
- const delay = delayMs ?? this.options.retryDelayMs;
15397
+ const { retryDelayMs } = this.options;
15395
15398
  let timeoutId;
15396
15399
  const endDelay = () => {
15397
15400
  resolve();
@@ -15402,7 +15405,7 @@ The next upload iteration will be delayed.`);
15402
15405
  signal?.removeEventListener('abort', endDelay);
15403
15406
  };
15404
15407
  signal?.addEventListener('abort', endDelay, { once: true });
15405
- timeoutId = setTimeout(endDelay, delay);
15408
+ timeoutId = setTimeout(endDelay, retryDelayMs);
15406
15409
  });
15407
15410
  }
15408
15411
  updateSubscriptions(subscriptions) {
@@ -15932,11 +15935,11 @@ class AbstractPowerSyncDatabase extends BaseObserver {
15932
15935
  .map((n) => parseInt(n));
15933
15936
  }
15934
15937
  catch (e) {
15935
- throw new Error(`Unsupported powersync extension version. Need >=0.4.5 <1.0.0, got: ${this.sdkVersion}. Details: ${e.message}`);
15938
+ throw new Error(`Unsupported powersync extension version. Need >=0.4.10 <1.0.0, got: ${this.sdkVersion}. Details: ${e.message}`);
15936
15939
  }
15937
- // Validate >=0.4.5 <1.0.0
15938
- if (versionInts[0] != 0 || versionInts[1] < 4 || (versionInts[1] == 4 && versionInts[2] < 5)) {
15939
- throw new Error(`Unsupported powersync extension version. Need >=0.4.5 <1.0.0, got: ${this.sdkVersion}`);
15940
+ // Validate >=0.4.10 <1.0.0
15941
+ if (versionInts[0] != 0 || versionInts[1] < 4 || (versionInts[1] == 4 && versionInts[2] < 10)) {
15942
+ throw new Error(`Unsupported powersync extension version. Need >=0.4.10 <1.0.0, got: ${this.sdkVersion}`);
15940
15943
  }
15941
15944
  }
15942
15945
  async resolveOfflineSyncStatus() {
@@ -17652,7 +17655,7 @@ __webpack_require__.r(__webpack_exports__);
17652
17655
  /* harmony export */ LockedAsyncDatabaseAdapter: () => (/* binding */ LockedAsyncDatabaseAdapter)
17653
17656
  /* harmony export */ });
17654
17657
  /* harmony import */ var _powersync_common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @powersync/common */ "../common/dist/bundle.mjs");
17655
- /* harmony import */ var _shared_navigator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../..//shared/navigator */ "./lib/src/shared/navigator.js");
17658
+ /* harmony import */ var _shared_navigator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../shared/navigator */ "./lib/src/shared/navigator.js");
17656
17659
  /* harmony import */ var _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./AsyncDatabaseConnection */ "./lib/src/db/adapters/AsyncDatabaseConnection.js");
17657
17660
  /* harmony import */ var _WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./WorkerWrappedAsyncDatabaseConnection */ "./lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js");
17658
17661
  /* harmony import */ var _wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./wa-sqlite/WASQLiteConnection */ "./lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js");
@@ -17729,21 +17732,28 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
17729
17732
  return this.initPromise;
17730
17733
  }
17731
17734
  async openInternalDB() {
17732
- // Dispose any previous table change listener.
17733
- this._disposeTableChangeListener?.();
17734
- this._disposeTableChangeListener = null;
17735
- const isReOpen = !!this._db;
17736
- this._db = await this.options.openConnection();
17737
- await this._db.init();
17738
- this._config = await this._db.getConfig();
17739
- await this.registerOnChangeListener(this._db);
17740
- if (isReOpen) {
17741
- this.iterateListeners((cb) => cb.databaseReOpened?.());
17742
- }
17743
17735
  /**
17744
- * This is only required for the long-lived shared IndexedDB connections.
17736
+ * Execute opening of the db in a lock in order not to interfere with other operations.
17745
17737
  */
17746
- this.requiresHolds = this._config.vfs == _wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__.WASQLiteVFS.IDBBatchAtomicVFS;
17738
+ return this._acquireLock(async () => {
17739
+ // Dispose any previous table change listener.
17740
+ this._disposeTableChangeListener?.();
17741
+ this._disposeTableChangeListener = null;
17742
+ this._db?.close().catch((ex) => this.logger.warn(`Error closing database before opening new instance`, ex));
17743
+ const isReOpen = !!this._db;
17744
+ this._db = null;
17745
+ this._db = await this.options.openConnection();
17746
+ await this._db.init();
17747
+ this._config = await this._db.getConfig();
17748
+ await this.registerOnChangeListener(this._db);
17749
+ if (isReOpen) {
17750
+ this.iterateListeners((cb) => cb.databaseReOpened?.());
17751
+ }
17752
+ /**
17753
+ * This is only required for the long-lived shared IndexedDB connections.
17754
+ */
17755
+ this.requiresHolds = this._config.vfs == _wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__.WASQLiteVFS.IDBBatchAtomicVFS;
17756
+ });
17747
17757
  }
17748
17758
  _reOpen() {
17749
17759
  this.databaseOpenPromise = this.openInternalDB().finally(() => {
@@ -17765,7 +17775,24 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
17765
17775
  return this._reOpen();
17766
17776
  }
17767
17777
  async _init() {
17768
- await this.openInternalDB();
17778
+ /**
17779
+ * For OPFS, we can see this open call sometimes fail due to NoModificationAllowedError.
17780
+ * We should be able to recover from this by re-opening the database.
17781
+ */
17782
+ const maxAttempts = 3;
17783
+ for (let count = 0; count < maxAttempts; count++) {
17784
+ try {
17785
+ await this.openInternalDB();
17786
+ break;
17787
+ }
17788
+ catch (ex) {
17789
+ if (count == maxAttempts - 1) {
17790
+ throw ex;
17791
+ }
17792
+ this.logger.warn(`Attempt ${count + 1} of ${maxAttempts} to open database failed, retrying in 1 second...`, ex);
17793
+ await new Promise((resolve) => setTimeout(resolve, 1000));
17794
+ }
17795
+ }
17769
17796
  this.iterateListeners((cb) => cb.initialized?.());
17770
17797
  }
17771
17798
  getConfiguration() {
@@ -17842,8 +17869,7 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
17842
17869
  timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
17843
17870
  });
17844
17871
  }
17845
- async acquireLock(callback, options) {
17846
- await this.waitForInitialized();
17872
+ async _acquireLock(callback, options) {
17847
17873
  if (this.closing) {
17848
17874
  throw new Error(`Cannot acquire lock, the database is closing`);
17849
17875
  }
@@ -17861,19 +17887,26 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
17861
17887
  if (timeoutId) {
17862
17888
  clearTimeout(timeoutId);
17863
17889
  }
17890
+ return await callback();
17891
+ });
17892
+ }
17893
+ async acquireLock(callback, options) {
17894
+ await this.waitForInitialized();
17895
+ // The database is being opened in the background. Wait for it here.
17896
+ if (this.databaseOpenPromise) {
17897
+ await this.databaseOpenPromise;
17898
+ }
17899
+ return this._acquireLock(async () => {
17864
17900
  let holdId = null;
17865
17901
  try {
17866
- // The database is being opened in the background. Wait for it here.
17867
- if (this.databaseOpenPromise) {
17868
- try {
17869
- await this.databaseOpenPromise;
17870
- }
17871
- catch (ex) {
17872
- // This will cause a retry of opening the database.
17873
- const wrappedError = new _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__.ConnectionClosedError('Could not open database');
17874
- wrappedError.cause = ex;
17875
- throw wrappedError;
17876
- }
17902
+ /**
17903
+ * We can't await this since it uses the same lock as we're in now.
17904
+ * If there is a pending open, this call will throw.
17905
+ * If there is no pending open, but there is also no database - the open
17906
+ * might have failed. We need to re-open the database.
17907
+ */
17908
+ if (this.databaseOpenPromise || !this._db) {
17909
+ throw new _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__.ConnectionClosedError('Connection is busy re-opening');
17877
17910
  }
17878
17911
  holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
17879
17912
  return await callback();
@@ -17882,6 +17915,7 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
17882
17915
  if (ex instanceof _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__.ConnectionClosedError) {
17883
17916
  if (this.options.reOpenOnConnectionClosed && !this.databaseOpenPromise && !this.closing) {
17884
17917
  // Immediately re-open the database. We need to miss as little table updates as possible.
17918
+ // Note, don't await this since it uses the same lock as we're in now.
17885
17919
  this.reOpenInternalDB();
17886
17920
  }
17887
17921
  }
@@ -17892,7 +17926,7 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
17892
17926
  await this.baseDB.releaseHold(holdId);
17893
17927
  }
17894
17928
  }
17895
- });
17929
+ }, options);
17896
17930
  }
17897
17931
  async readTransaction(fn, options) {
17898
17932
  return this.readLock(this.wrapTransaction(fn));
@@ -18277,9 +18311,10 @@ const DEFAULT_MODULE_FACTORIES = {
18277
18311
  }
18278
18312
  // @ts-expect-error The types for this static method are missing upstream
18279
18313
  const { OPFSCoopSyncVFS } = await __webpack_require__.e(/*! import() */ "node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js").then(__webpack_require__.bind(__webpack_require__, /*! @journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js */ "../../node_modules/@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js"));
18314
+ const vfs = await OPFSCoopSyncVFS.create(options.dbFileName, module);
18280
18315
  return {
18281
18316
  module,
18282
- vfs: await OPFSCoopSyncVFS.create(options.dbFileName, module)
18317
+ vfs
18283
18318
  };
18284
18319
  }
18285
18320
  };
@@ -18505,7 +18540,15 @@ class WASqliteConnection extends _powersync_common__WEBPACK_IMPORTED_MODULE_1__.
18505
18540
  }
18506
18541
  async close() {
18507
18542
  this.broadcastChannel?.close();
18508
- await this.sqliteAPI.close(this.dbP);
18543
+ await this.acquireExecuteLock(async () => {
18544
+ /**
18545
+ * Running the close operation inside the same execute mutex prevents errors like:
18546
+ * ```
18547
+ * unable to close due to unfinalized statements or unfinished backups
18548
+ * ```
18549
+ */
18550
+ await this.sqliteAPI.close(this.dbP);
18551
+ });
18509
18552
  }
18510
18553
  async registerOnTableChange(callback) {
18511
18554
  return this.registerListener({
@@ -19051,7 +19094,8 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
19051
19094
  */
19052
19095
  async getRandomWrappedPort() {
19053
19096
  return await this.portMutex.runExclusive(() => {
19054
- return this.ports[Math.floor(Math.random() * this.ports.length)];
19097
+ const nonClosingPorts = this.ports.filter((p) => !p.isClosing);
19098
+ return nonClosingPorts[Math.floor(Math.random() * nonClosingPorts.length)];
19055
19099
  });
19056
19100
  }
19057
19101
  async waitForStatus(status) {
@@ -19318,73 +19362,77 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
19318
19362
  * Opens a worker wrapped database connection. Using the last connected client port.
19319
19363
  */
19320
19364
  async openInternalDB() {
19321
- while (true) {
19322
- try {
19323
- const client = await this.getRandomWrappedPort();
19324
- if (!client) {
19325
- // Should not really happen in practice
19326
- throw new Error(`Could not open DB connection since no client is connected.`);
19327
- }
19328
- // Fail-safe timeout for opening a database connection.
19329
- const timeout = setTimeout(() => {
19330
- abortController.abort();
19331
- }, 10_000);
19332
- /**
19333
- * Handle cases where the client might close while opening a connection.
19334
- */
19335
- const abortController = new AbortController();
19336
- const closeListener = () => {
19337
- abortController.abort();
19338
- };
19339
- const removeCloseListener = () => {
19340
- const index = client.closeListeners.indexOf(closeListener);
19341
- if (index >= 0) {
19342
- client.closeListeners.splice(index, 1);
19343
- }
19344
- };
19345
- client.closeListeners.push(closeListener);
19346
- const workerPort = await withAbort(() => client.clientProvider.getDBWorkerPort(), abortController.signal).catch((ex) => {
19347
- removeCloseListener();
19348
- throw ex;
19349
- });
19350
- const remote = comlink__WEBPACK_IMPORTED_MODULE_7__.wrap(workerPort);
19351
- const identifier = this.syncParams.dbParams.dbFilename;
19352
- /**
19353
- * The open could fail if the tab is closed while we're busy opening the database.
19354
- * This operation is typically executed inside an exclusive portMutex lock.
19355
- * We typically execute the closeListeners using the portMutex in a different context.
19356
- * We can't rely on the closeListeners to abort the operation if the tab is closed.
19357
- */
19358
- const db = await withAbort(() => remote(this.syncParams.dbParams), abortController.signal).finally(() => {
19359
- // We can remove the close listener here since we no longer need it past this point.
19360
- removeCloseListener();
19361
- });
19362
- clearTimeout(timeout);
19363
- const wrapped = new _db_adapters_WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_5__.WorkerWrappedAsyncDatabaseConnection({
19364
- remote,
19365
- baseConnection: db,
19366
- identifier,
19367
- // It's possible for this worker to outlive the client hosting the database for us. We need to be prepared for
19368
- // that and ensure pending requests are aborted when the tab is closed.
19369
- remoteCanCloseUnexpectedly: true
19370
- });
19371
- client.closeListeners.push(async () => {
19372
- this.logger.info('Aborting open connection because associated tab closed.');
19373
- /**
19374
- * Don't await this close operation. It might never resolve if the tab is closed.
19375
- * We mark the remote as closed first, this will reject any pending requests.
19376
- * We then call close. The close operation is configured to fire-and-forget, the main promise will reject immediately.
19377
- */
19378
- wrapped.markRemoteClosed();
19379
- wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
19380
- });
19381
- return wrapped;
19365
+ const client = await this.getRandomWrappedPort();
19366
+ if (!client) {
19367
+ // Should not really happen in practice
19368
+ throw new Error(`Could not open DB connection since no client is connected.`);
19369
+ }
19370
+ // Fail-safe timeout for opening a database connection.
19371
+ const timeout = setTimeout(() => {
19372
+ abortController.abort();
19373
+ }, 10_000);
19374
+ /**
19375
+ * Handle cases where the client might close while opening a connection.
19376
+ */
19377
+ const abortController = new AbortController();
19378
+ const closeListener = () => {
19379
+ abortController.abort();
19380
+ };
19381
+ const removeCloseListener = () => {
19382
+ const index = client.closeListeners.indexOf(closeListener);
19383
+ if (index >= 0) {
19384
+ client.closeListeners.splice(index, 1);
19382
19385
  }
19383
- catch (ex) {
19384
- this.logger.warn('Error opening internal DB', ex);
19385
- await new Promise((resolve) => setTimeout(resolve, 1000));
19386
+ };
19387
+ client.closeListeners.push(closeListener);
19388
+ const workerPort = await withAbort({
19389
+ action: () => client.clientProvider.getDBWorkerPort(),
19390
+ signal: abortController.signal,
19391
+ cleanupOnAbort: (port) => {
19392
+ port.close();
19386
19393
  }
19387
- }
19394
+ }).catch((ex) => {
19395
+ removeCloseListener();
19396
+ throw ex;
19397
+ });
19398
+ const remote = comlink__WEBPACK_IMPORTED_MODULE_7__.wrap(workerPort);
19399
+ const identifier = this.syncParams.dbParams.dbFilename;
19400
+ /**
19401
+ * The open could fail if the tab is closed while we're busy opening the database.
19402
+ * This operation is typically executed inside an exclusive portMutex lock.
19403
+ * We typically execute the closeListeners using the portMutex in a different context.
19404
+ * We can't rely on the closeListeners to abort the operation if the tab is closed.
19405
+ */
19406
+ const db = await withAbort({
19407
+ action: () => remote(this.syncParams.dbParams),
19408
+ signal: abortController.signal,
19409
+ cleanupOnAbort: (db) => {
19410
+ db.close();
19411
+ }
19412
+ }).finally(() => {
19413
+ // We can remove the close listener here since we no longer need it past this point.
19414
+ removeCloseListener();
19415
+ });
19416
+ clearTimeout(timeout);
19417
+ const wrapped = new _db_adapters_WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_5__.WorkerWrappedAsyncDatabaseConnection({
19418
+ remote,
19419
+ baseConnection: db,
19420
+ identifier,
19421
+ // It's possible for this worker to outlive the client hosting the database for us. We need to be prepared for
19422
+ // that and ensure pending requests are aborted when the tab is closed.
19423
+ remoteCanCloseUnexpectedly: true
19424
+ });
19425
+ client.closeListeners.push(async () => {
19426
+ this.logger.info('Aborting open connection because associated tab closed.');
19427
+ /**
19428
+ * Don't await this close operation. It might never resolve if the tab is closed.
19429
+ * We mark the remote as closed first, this will reject any pending requests.
19430
+ * We then call close. The close operation is configured to fire-and-forget, the main promise will reject immediately.
19431
+ */
19432
+ wrapped.markRemoteClosed();
19433
+ wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
19434
+ });
19435
+ return wrapped;
19388
19436
  }
19389
19437
  /**
19390
19438
  * A method to update the all shared statuses for each
@@ -19398,7 +19446,8 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
19398
19446
  /**
19399
19447
  * Runs the action with an abort controller.
19400
19448
  */
19401
- function withAbort(action, signal) {
19449
+ function withAbort(options) {
19450
+ const { action, signal, cleanupOnAbort } = options;
19402
19451
  return new Promise((resolve, reject) => {
19403
19452
  if (signal.aborted) {
19404
19453
  reject(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Operation aborted by abort controller'));
@@ -19414,7 +19463,13 @@ function withAbort(action, signal) {
19414
19463
  action();
19415
19464
  }
19416
19465
  action()
19417
- .then((data) => completePromise(() => resolve(data)))
19466
+ .then((data) => {
19467
+ // We already rejected due to the abort, allow for cleanup
19468
+ if (signal.aborted) {
19469
+ return completePromise(() => cleanupOnAbort?.(data));
19470
+ }
19471
+ completePromise(() => resolve(data));
19472
+ })
19418
19473
  .catch((e) => completePromise(() => reject(e)));
19419
19474
  });
19420
19475
  }