@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.
- package/dist/0b19af1befc07ce338dd.wasm +0 -0
- package/dist/2632c3bda9473da74fd5.wasm +0 -0
- package/dist/64f5351ba3784bfe2f3e.wasm +0 -0
- package/dist/9318ca94aac4d0fe0135.wasm +0 -0
- package/dist/index.umd.js +219 -115
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +164 -109
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +24 -12
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +16 -3
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +16 -3
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +16 -3
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +16 -3
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js +18 -11
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js.map +1 -1
- package/lib/package.json +2 -2
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +4 -1
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +60 -29
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +11 -2
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +1 -1
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +2 -2
- package/lib/src/worker/sync/SharedSyncImplementation.js +80 -68
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +79 -48
- package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +11 -3
- package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +3 -3
- package/src/worker/db/WASQLiteDB.worker.ts +0 -1
- package/src/worker/sync/SharedSyncImplementation.ts +89 -74
- package/dist/1807036ae51c10ee4d23.wasm +0 -0
- package/dist/307d8ce2280e3bae09d5.wasm +0 -0
- package/dist/cd8b9e8f4c87bf81c169.wasm +0 -0
- package/dist/e797080f5ed0b5324166.wasm +0 -0
- package/lib/src/worker/sync/MockSyncService.d.ts +0 -2
- package/lib/src/worker/sync/MockSyncService.js +0 -3
- package/lib/src/worker/sync/MockSyncServiceTypes.d.ts +0 -101
- package/lib/src/worker/sync/MockSyncServiceTypes.js +0 -1
- package/lib/src/worker/sync/MockSyncServiceWorker.d.ts +0 -56
- package/lib/src/worker/sync/MockSyncServiceWorker.js +0 -369
- package/src/worker/sync/MockSyncService.ts +0 -3
- package/src/worker/sync/MockSyncServiceTypes.ts +0 -71
- 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
|
-
|
|
1151
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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.
|
|
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.
|
|
15938
|
-
if (versionInts[0] != 0 || versionInts[1] < 4 || (versionInts[1] == 4 && versionInts[2] <
|
|
15939
|
-
throw new Error(`Unsupported powersync extension version. Need >=0.4.
|
|
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__(/*!
|
|
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
|
-
*
|
|
17736
|
+
* Execute opening of the db in a lock in order not to interfere with other operations.
|
|
17745
17737
|
*/
|
|
17746
|
-
this.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
|
|
17871
|
-
|
|
17872
|
-
|
|
17873
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
|
|
19331
|
-
|
|
19332
|
-
|
|
19333
|
-
|
|
19334
|
-
|
|
19335
|
-
|
|
19336
|
-
|
|
19337
|
-
|
|
19338
|
-
|
|
19339
|
-
|
|
19340
|
-
|
|
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
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
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(
|
|
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) =>
|
|
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
|
}
|