@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 +265 -127
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +255 -105
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +4 -4
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +3 -0
- package/lib/src/db/adapters/AsyncDatabaseConnection.js +6 -1
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +9 -0
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +46 -8
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +3 -2
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +2 -1
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +48 -27
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +18 -3
- package/lib/src/worker/sync/SharedSyncImplementation.js +143 -76
- package/lib/src/worker/sync/WorkerClient.d.ts +3 -2
- package/lib/src/worker/sync/WorkerClient.js +26 -4
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/db/adapters/AsyncDatabaseConnection.ts +7 -0
- package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +54 -9
- package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +3 -2
- package/src/db/sync/SharedWebStreamingSyncImplementation.ts +60 -36
- package/src/worker/sync/SharedSyncImplementation.ts +171 -87
- package/src/worker/sync/WorkerClient.ts +28 -6
|
@@ -14659,7 +14659,7 @@ The next upload iteration will be delayed.`);
|
|
|
14659
14659
|
uploadError: ex
|
|
14660
14660
|
}
|
|
14661
14661
|
});
|
|
14662
|
-
await this.delayRetry(controller.signal);
|
|
14662
|
+
await this.delayRetry(controller.signal, this.options.crudUploadThrottleMs);
|
|
14663
14663
|
if (!this.isConnected) {
|
|
14664
14664
|
// Exit the upload loop if the sync stream is no longer connected
|
|
14665
14665
|
break;
|
|
@@ -15370,14 +15370,14 @@ The next upload iteration will be delayed.`);
|
|
|
15370
15370
|
// trigger this for all updates
|
|
15371
15371
|
this.iterateListeners((cb) => cb.statusUpdated?.(options));
|
|
15372
15372
|
}
|
|
15373
|
-
async delayRetry(signal) {
|
|
15373
|
+
async delayRetry(signal, delayMs) {
|
|
15374
15374
|
return new Promise((resolve) => {
|
|
15375
15375
|
if (signal?.aborted) {
|
|
15376
15376
|
// If the signal is already aborted, resolve immediately
|
|
15377
15377
|
resolve();
|
|
15378
15378
|
return;
|
|
15379
15379
|
}
|
|
15380
|
-
const
|
|
15380
|
+
const delay = delayMs ?? this.options.retryDelayMs;
|
|
15381
15381
|
let timeoutId;
|
|
15382
15382
|
const endDelay = () => {
|
|
15383
15383
|
resolve();
|
|
@@ -15388,7 +15388,7 @@ The next upload iteration will be delayed.`);
|
|
|
15388
15388
|
signal?.removeEventListener('abort', endDelay);
|
|
15389
15389
|
};
|
|
15390
15390
|
signal?.addEventListener('abort', endDelay, { once: true });
|
|
15391
|
-
timeoutId = setTimeout(endDelay,
|
|
15391
|
+
timeoutId = setTimeout(endDelay, delay);
|
|
15392
15392
|
});
|
|
15393
15393
|
}
|
|
15394
15394
|
updateSubscriptions(subscriptions) {
|
|
@@ -17600,6 +17600,26 @@ const parseQuery = (query, parameters) => {
|
|
|
17600
17600
|
//# sourceMappingURL=bundle.mjs.map
|
|
17601
17601
|
|
|
17602
17602
|
|
|
17603
|
+
/***/ }),
|
|
17604
|
+
|
|
17605
|
+
/***/ "./lib/src/db/adapters/AsyncDatabaseConnection.js":
|
|
17606
|
+
/*!********************************************************!*\
|
|
17607
|
+
!*** ./lib/src/db/adapters/AsyncDatabaseConnection.js ***!
|
|
17608
|
+
\********************************************************/
|
|
17609
|
+
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
17610
|
+
|
|
17611
|
+
__webpack_require__.r(__webpack_exports__);
|
|
17612
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
17613
|
+
/* harmony export */ ConnectionClosedError: () => (/* binding */ ConnectionClosedError)
|
|
17614
|
+
/* harmony export */ });
|
|
17615
|
+
class ConnectionClosedError extends Error {
|
|
17616
|
+
constructor(message) {
|
|
17617
|
+
super(message);
|
|
17618
|
+
this.name = 'ConnectionClosedError';
|
|
17619
|
+
}
|
|
17620
|
+
}
|
|
17621
|
+
|
|
17622
|
+
|
|
17603
17623
|
/***/ }),
|
|
17604
17624
|
|
|
17605
17625
|
/***/ "./lib/src/db/adapters/LockedAsyncDatabaseAdapter.js":
|
|
@@ -17614,8 +17634,10 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
17614
17634
|
/* harmony export */ });
|
|
17615
17635
|
/* harmony import */ var _powersync_common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @powersync/common */ "../common/dist/bundle.mjs");
|
|
17616
17636
|
/* harmony import */ var _shared_navigator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../..//shared/navigator */ "./lib/src/shared/navigator.js");
|
|
17617
|
-
/* harmony import */ var
|
|
17618
|
-
/* harmony import */ var
|
|
17637
|
+
/* harmony import */ var _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./AsyncDatabaseConnection */ "./lib/src/db/adapters/AsyncDatabaseConnection.js");
|
|
17638
|
+
/* harmony import */ var _WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./WorkerWrappedAsyncDatabaseConnection */ "./lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js");
|
|
17639
|
+
/* harmony import */ var _wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./wa-sqlite/WASQLiteConnection */ "./lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js");
|
|
17640
|
+
|
|
17619
17641
|
|
|
17620
17642
|
|
|
17621
17643
|
|
|
@@ -17637,6 +17659,8 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
|
|
|
17637
17659
|
_config = null;
|
|
17638
17660
|
pendingAbortControllers;
|
|
17639
17661
|
requiresHolds;
|
|
17662
|
+
requiresReOpen;
|
|
17663
|
+
databaseOpenPromise = null;
|
|
17640
17664
|
closing;
|
|
17641
17665
|
closed;
|
|
17642
17666
|
constructor(options) {
|
|
@@ -17648,6 +17672,7 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
|
|
|
17648
17672
|
this.closed = false;
|
|
17649
17673
|
this.closing = false;
|
|
17650
17674
|
this.requiresHolds = null;
|
|
17675
|
+
this.requiresReOpen = false;
|
|
17651
17676
|
// Set the name if provided. We can query for the name if not available yet
|
|
17652
17677
|
this.debugMode = options.debugMode ?? false;
|
|
17653
17678
|
if (this.debugMode) {
|
|
@@ -17686,16 +17711,26 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
|
|
|
17686
17711
|
async init() {
|
|
17687
17712
|
return this.initPromise;
|
|
17688
17713
|
}
|
|
17689
|
-
async
|
|
17714
|
+
async openInternalDB() {
|
|
17715
|
+
// Dispose any previous table change listener.
|
|
17716
|
+
this._disposeTableChangeListener?.();
|
|
17717
|
+
this._disposeTableChangeListener = null;
|
|
17718
|
+
const isReOpen = !!this._db;
|
|
17690
17719
|
this._db = await this.options.openConnection();
|
|
17691
17720
|
await this._db.init();
|
|
17692
17721
|
this._config = await this._db.getConfig();
|
|
17693
17722
|
await this.registerOnChangeListener(this._db);
|
|
17694
|
-
|
|
17723
|
+
if (isReOpen) {
|
|
17724
|
+
this.iterateListeners((cb) => cb.databaseReOpened?.());
|
|
17725
|
+
}
|
|
17695
17726
|
/**
|
|
17696
17727
|
* This is only required for the long-lived shared IndexedDB connections.
|
|
17697
17728
|
*/
|
|
17698
|
-
this.requiresHolds = this._config.vfs ==
|
|
17729
|
+
this.requiresHolds = this._config.vfs == _wa_sqlite_WASQLiteConnection__WEBPACK_IMPORTED_MODULE_4__.WASQLiteVFS.IDBBatchAtomicVFS;
|
|
17730
|
+
}
|
|
17731
|
+
async _init() {
|
|
17732
|
+
await this.openInternalDB();
|
|
17733
|
+
this.iterateListeners((cb) => cb.initialized?.());
|
|
17699
17734
|
}
|
|
17700
17735
|
getConfiguration() {
|
|
17701
17736
|
if (!this._config) {
|
|
@@ -17708,7 +17743,7 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
|
|
|
17708
17743
|
await this.initPromise;
|
|
17709
17744
|
}
|
|
17710
17745
|
async shareConnection() {
|
|
17711
|
-
if (false == this._db instanceof
|
|
17746
|
+
if (false == this._db instanceof _WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_3__.WorkerWrappedAsyncDatabaseConnection) {
|
|
17712
17747
|
throw new Error(`Only worker connections can be shared`);
|
|
17713
17748
|
}
|
|
17714
17749
|
return this._db.shareConnection();
|
|
@@ -17762,13 +17797,13 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
|
|
|
17762
17797
|
async readLock(fn, options) {
|
|
17763
17798
|
await this.waitForInitialized();
|
|
17764
17799
|
return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
|
|
17765
|
-
timeoutMs: options?.timeoutMs
|
|
17800
|
+
timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
|
|
17766
17801
|
});
|
|
17767
17802
|
}
|
|
17768
17803
|
async writeLock(fn, options) {
|
|
17769
17804
|
await this.waitForInitialized();
|
|
17770
17805
|
return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
|
|
17771
|
-
timeoutMs: options?.timeoutMs
|
|
17806
|
+
timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
|
|
17772
17807
|
});
|
|
17773
17808
|
}
|
|
17774
17809
|
async acquireLock(callback, options) {
|
|
@@ -17779,7 +17814,7 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
|
|
|
17779
17814
|
const abortController = new AbortController();
|
|
17780
17815
|
this.pendingAbortControllers.add(abortController);
|
|
17781
17816
|
const { timeoutMs } = options ?? {};
|
|
17782
|
-
const
|
|
17817
|
+
const timeoutId = timeoutMs
|
|
17783
17818
|
? setTimeout(() => {
|
|
17784
17819
|
abortController.abort(`Timeout after ${timeoutMs}ms`);
|
|
17785
17820
|
this.pendingAbortControllers.delete(abortController);
|
|
@@ -17787,13 +17822,37 @@ class LockedAsyncDatabaseAdapter extends _powersync_common__WEBPACK_IMPORTED_MOD
|
|
|
17787
17822
|
: null;
|
|
17788
17823
|
return (0,_shared_navigator__WEBPACK_IMPORTED_MODULE_1__.getNavigatorLocks)().request(`db-lock-${this._dbIdentifier}`, { signal: abortController.signal }, async () => {
|
|
17789
17824
|
this.pendingAbortControllers.delete(abortController);
|
|
17790
|
-
if (
|
|
17791
|
-
clearTimeout(
|
|
17825
|
+
if (timeoutId) {
|
|
17826
|
+
clearTimeout(timeoutId);
|
|
17792
17827
|
}
|
|
17793
|
-
|
|
17828
|
+
let holdId = null;
|
|
17794
17829
|
try {
|
|
17830
|
+
// The database is being opened in the background. Wait for it here.
|
|
17831
|
+
if (this.databaseOpenPromise) {
|
|
17832
|
+
try {
|
|
17833
|
+
await this.databaseOpenPromise;
|
|
17834
|
+
}
|
|
17835
|
+
catch (ex) {
|
|
17836
|
+
// This will cause a retry of opening the database.
|
|
17837
|
+
const wrappedError = new _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__.ConnectionClosedError('Could not open database');
|
|
17838
|
+
wrappedError.cause = ex;
|
|
17839
|
+
throw wrappedError;
|
|
17840
|
+
}
|
|
17841
|
+
}
|
|
17842
|
+
holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
|
|
17795
17843
|
return await callback();
|
|
17796
17844
|
}
|
|
17845
|
+
catch (ex) {
|
|
17846
|
+
if (ex instanceof _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_2__.ConnectionClosedError) {
|
|
17847
|
+
if (this.options.reOpenOnConnectionClosed && !this.databaseOpenPromise && !this.closing) {
|
|
17848
|
+
// Immediately re-open the database. We need to miss as little table updates as possible.
|
|
17849
|
+
this.databaseOpenPromise = this.openInternalDB().finally(() => {
|
|
17850
|
+
this.databaseOpenPromise = null;
|
|
17851
|
+
});
|
|
17852
|
+
}
|
|
17853
|
+
}
|
|
17854
|
+
throw ex;
|
|
17855
|
+
}
|
|
17797
17856
|
finally {
|
|
17798
17857
|
if (holdId) {
|
|
17799
17858
|
await this.baseDB.releaseHold(holdId);
|
|
@@ -17926,7 +17985,9 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
17926
17985
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
17927
17986
|
/* harmony export */ WorkerWrappedAsyncDatabaseConnection: () => (/* binding */ WorkerWrappedAsyncDatabaseConnection)
|
|
17928
17987
|
/* harmony export */ });
|
|
17929
|
-
/* harmony import */ var
|
|
17988
|
+
/* harmony import */ var comlink__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! comlink */ "../../node_modules/comlink/dist/esm/comlink.mjs");
|
|
17989
|
+
/* harmony import */ var _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AsyncDatabaseConnection */ "./lib/src/db/adapters/AsyncDatabaseConnection.js");
|
|
17990
|
+
|
|
17930
17991
|
|
|
17931
17992
|
/**
|
|
17932
17993
|
* Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
|
|
@@ -17974,12 +18035,12 @@ class WorkerWrappedAsyncDatabaseConnection {
|
|
|
17974
18035
|
if (controller) {
|
|
17975
18036
|
return new Promise((resolve, reject) => {
|
|
17976
18037
|
if (controller.signal.aborted) {
|
|
17977
|
-
reject(new
|
|
18038
|
+
reject(new _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_0__.ConnectionClosedError('Called operation on closed remote'));
|
|
17978
18039
|
// Don't run the operation if we're going to reject
|
|
17979
18040
|
return;
|
|
17980
18041
|
}
|
|
17981
18042
|
function handleAbort() {
|
|
17982
|
-
reject(new
|
|
18043
|
+
reject(new _AsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_0__.ConnectionClosedError('Remote peer closed with request in flight'));
|
|
17983
18044
|
}
|
|
17984
18045
|
function completePromise(action) {
|
|
17985
18046
|
controller.signal.removeEventListener('abort', handleAbort);
|
|
@@ -18033,7 +18094,7 @@ class WorkerWrappedAsyncDatabaseConnection {
|
|
|
18033
18094
|
reject(ex);
|
|
18034
18095
|
}
|
|
18035
18096
|
}));
|
|
18036
|
-
const newPort = await remote[
|
|
18097
|
+
const newPort = await remote[comlink__WEBPACK_IMPORTED_MODULE_1__.createEndpoint]();
|
|
18037
18098
|
return { port: newPort, identifier };
|
|
18038
18099
|
}
|
|
18039
18100
|
/**
|
|
@@ -18041,7 +18102,7 @@ class WorkerWrappedAsyncDatabaseConnection {
|
|
|
18041
18102
|
* This can be extended by custom implementations in order to handle proxy events.
|
|
18042
18103
|
*/
|
|
18043
18104
|
async registerOnTableChange(callback) {
|
|
18044
|
-
return this.baseConnection.registerOnTableChange(
|
|
18105
|
+
return this.baseConnection.registerOnTableChange(comlink__WEBPACK_IMPORTED_MODULE_1__.proxy(callback));
|
|
18045
18106
|
}
|
|
18046
18107
|
async close() {
|
|
18047
18108
|
// Abort any pending lock requests.
|
|
@@ -18050,7 +18111,7 @@ class WorkerWrappedAsyncDatabaseConnection {
|
|
|
18050
18111
|
await this.withRemote(() => this.baseConnection.close());
|
|
18051
18112
|
}
|
|
18052
18113
|
finally {
|
|
18053
|
-
this.options.remote[
|
|
18114
|
+
this.options.remote[comlink__WEBPACK_IMPORTED_MODULE_1__.releaseProxy]();
|
|
18054
18115
|
this.options.onClose?.();
|
|
18055
18116
|
}
|
|
18056
18117
|
}
|
|
@@ -18877,7 +18938,6 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
18877
18938
|
statusListener;
|
|
18878
18939
|
fetchCredentialsController;
|
|
18879
18940
|
uploadDataController;
|
|
18880
|
-
dbAdapter;
|
|
18881
18941
|
syncParams;
|
|
18882
18942
|
logger;
|
|
18883
18943
|
lastConnectOptions;
|
|
@@ -18886,10 +18946,10 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
18886
18946
|
connectionManager;
|
|
18887
18947
|
syncStatus;
|
|
18888
18948
|
broadCastLogger;
|
|
18949
|
+
distributedDB;
|
|
18889
18950
|
constructor() {
|
|
18890
18951
|
super();
|
|
18891
18952
|
this.ports = [];
|
|
18892
|
-
this.dbAdapter = null;
|
|
18893
18953
|
this.syncParams = null;
|
|
18894
18954
|
this.logger = (0,_powersync_common__WEBPACK_IMPORTED_MODULE_0__.createLogger)('shared-sync');
|
|
18895
18955
|
this.lastConnectOptions = undefined;
|
|
@@ -18902,26 +18962,23 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
18902
18962
|
}
|
|
18903
18963
|
});
|
|
18904
18964
|
});
|
|
18965
|
+
// Should be configured once we get params
|
|
18966
|
+
this.distributedDB = null;
|
|
18905
18967
|
this.syncStatus = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SyncStatus({});
|
|
18906
18968
|
this.broadCastLogger = new _BroadcastLogger__WEBPACK_IMPORTED_MODULE_6__.BroadcastLogger(this.ports);
|
|
18907
18969
|
this.connectionManager = new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.ConnectionManager({
|
|
18908
18970
|
createSyncImplementation: async () => {
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18971
|
+
await this.waitForReady();
|
|
18972
|
+
const sync = this.generateStreamingImplementation();
|
|
18973
|
+
const onDispose = sync.registerListener({
|
|
18974
|
+
statusChanged: (status) => {
|
|
18975
|
+
this.updateAllStatuses(status.toJSON());
|
|
18913
18976
|
}
|
|
18914
|
-
const sync = this.generateStreamingImplementation();
|
|
18915
|
-
const onDispose = sync.registerListener({
|
|
18916
|
-
statusChanged: (status) => {
|
|
18917
|
-
this.updateAllStatuses(status.toJSON());
|
|
18918
|
-
}
|
|
18919
|
-
});
|
|
18920
|
-
return {
|
|
18921
|
-
sync,
|
|
18922
|
-
onDispose
|
|
18923
|
-
};
|
|
18924
18977
|
});
|
|
18978
|
+
return {
|
|
18979
|
+
sync,
|
|
18980
|
+
onDispose
|
|
18981
|
+
};
|
|
18925
18982
|
},
|
|
18926
18983
|
logger: this.logger
|
|
18927
18984
|
});
|
|
@@ -18932,6 +18989,18 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
18932
18989
|
get isConnected() {
|
|
18933
18990
|
return this.connectionManager.syncStreamImplementation?.isConnected ?? false;
|
|
18934
18991
|
}
|
|
18992
|
+
/**
|
|
18993
|
+
* Gets the last client port which we know is safe from unexpected closes.
|
|
18994
|
+
*/
|
|
18995
|
+
get lastWrappedPort() {
|
|
18996
|
+
// Find the last port which is protected from close
|
|
18997
|
+
for (let i = this.ports.length - 1; i >= 0; i--) {
|
|
18998
|
+
if (this.ports[i].isProtectedFromClose && !this.ports[i].isClosing) {
|
|
18999
|
+
return this.ports[i];
|
|
19000
|
+
}
|
|
19001
|
+
}
|
|
19002
|
+
return;
|
|
19003
|
+
}
|
|
18935
19004
|
async waitForStatus(status) {
|
|
18936
19005
|
return this.withSyncImplementation(async (sync) => {
|
|
18937
19006
|
return sync.waitForStatus(status);
|
|
@@ -18974,10 +19043,6 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
18974
19043
|
this.collectActiveSubscriptions();
|
|
18975
19044
|
if (this.syncParams) {
|
|
18976
19045
|
// Cannot modify already existing sync implementation params
|
|
18977
|
-
// But we can ask for a DB adapter, if required, at this point.
|
|
18978
|
-
if (!this.dbAdapter) {
|
|
18979
|
-
await this.openInternalDB();
|
|
18980
|
-
}
|
|
18981
19046
|
return;
|
|
18982
19047
|
}
|
|
18983
19048
|
// First time setting params
|
|
@@ -18985,13 +19050,28 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
18985
19050
|
if (params.streamOptions?.flags?.broadcastLogs) {
|
|
18986
19051
|
this.logger = this.broadCastLogger;
|
|
18987
19052
|
}
|
|
19053
|
+
const lockedAdapter = new _db_adapters_LockedAsyncDatabaseAdapter__WEBPACK_IMPORTED_MODULE_4__.LockedAsyncDatabaseAdapter({
|
|
19054
|
+
name: params.dbParams.dbFilename,
|
|
19055
|
+
openConnection: async () => {
|
|
19056
|
+
// Gets a connection from the clients when a new connection is requested.
|
|
19057
|
+
return await this.openInternalDB();
|
|
19058
|
+
},
|
|
19059
|
+
logger: this.logger,
|
|
19060
|
+
reOpenOnConnectionClosed: true
|
|
19061
|
+
});
|
|
19062
|
+
this.distributedDB = lockedAdapter;
|
|
19063
|
+
await lockedAdapter.init();
|
|
19064
|
+
lockedAdapter.registerListener({
|
|
19065
|
+
databaseReOpened: () => {
|
|
19066
|
+
// We may have missed some table updates while the database was closed.
|
|
19067
|
+
// We can poke the crud in case we missed any updates.
|
|
19068
|
+
this.connectionManager.syncStreamImplementation?.triggerCrudUpload();
|
|
19069
|
+
}
|
|
19070
|
+
});
|
|
18988
19071
|
self.onerror = (event) => {
|
|
18989
19072
|
// Share any uncaught events on the broadcast logger
|
|
18990
19073
|
this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
|
|
18991
19074
|
};
|
|
18992
|
-
if (!this.dbAdapter) {
|
|
18993
|
-
await this.openInternalDB();
|
|
18994
|
-
}
|
|
18995
19075
|
this.iterateListeners((l) => l.initialized?.());
|
|
18996
19076
|
});
|
|
18997
19077
|
}
|
|
@@ -19022,7 +19102,9 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
19022
19102
|
port,
|
|
19023
19103
|
clientProvider: comlink__WEBPACK_IMPORTED_MODULE_7__.wrap(port),
|
|
19024
19104
|
currentSubscriptions: [],
|
|
19025
|
-
closeListeners: []
|
|
19105
|
+
closeListeners: [],
|
|
19106
|
+
isProtectedFromClose: false,
|
|
19107
|
+
isClosing: false
|
|
19026
19108
|
};
|
|
19027
19109
|
this.ports.push(portProvider);
|
|
19028
19110
|
// Give the newly connected client the latest status
|
|
@@ -19038,14 +19120,16 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
19038
19120
|
* clients.
|
|
19039
19121
|
*/
|
|
19040
19122
|
async removePort(port) {
|
|
19123
|
+
// Ports might be removed faster than we can process them.
|
|
19124
|
+
port.isClosing = true;
|
|
19041
19125
|
// Remove the port within a mutex context.
|
|
19042
19126
|
// Warns if the port is not found. This should not happen in practice.
|
|
19043
19127
|
// We return early if the port is not found.
|
|
19044
|
-
|
|
19128
|
+
return await this.portMutex.runExclusive(async () => {
|
|
19045
19129
|
const index = this.ports.findIndex((p) => p == port);
|
|
19046
19130
|
if (index < 0) {
|
|
19047
19131
|
this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`);
|
|
19048
|
-
return {};
|
|
19132
|
+
return () => { };
|
|
19049
19133
|
}
|
|
19050
19134
|
const trackedPort = this.ports[index];
|
|
19051
19135
|
// Remove from the list of active ports
|
|
@@ -19059,35 +19143,13 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
19059
19143
|
abortController.controller.abort(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Closing pending requests after client port is removed'));
|
|
19060
19144
|
}
|
|
19061
19145
|
});
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
trackedPort
|
|
19066
|
-
};
|
|
19067
|
-
});
|
|
19068
|
-
if (!trackedPort) {
|
|
19069
|
-
// We could not find the port to remove
|
|
19070
|
-
return () => { };
|
|
19071
|
-
}
|
|
19072
|
-
for (const closeListener of trackedPort.closeListeners) {
|
|
19073
|
-
await closeListener();
|
|
19074
|
-
}
|
|
19075
|
-
if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
|
|
19076
|
-
// Unconditionally close the connection because the database it's writing to has just been closed.
|
|
19077
|
-
// The connection has been closed previously, this might throw. We should be able to ignore it.
|
|
19078
|
-
await this.connectionManager
|
|
19079
|
-
.disconnect()
|
|
19080
|
-
.catch((ex) => this.logger.warn('Error while disconnecting. Will attempt to reconnect.', ex));
|
|
19081
|
-
// Clearing the adapter will result in a new one being opened in connect
|
|
19082
|
-
this.dbAdapter = null;
|
|
19083
|
-
if (shouldReconnect) {
|
|
19084
|
-
await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions ?? {});
|
|
19146
|
+
// Close the worker wrapped database connection, we can't accurately rely on this connection
|
|
19147
|
+
for (const closeListener of trackedPort.closeListeners) {
|
|
19148
|
+
await closeListener();
|
|
19085
19149
|
}
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
// Release proxy
|
|
19090
|
-
return () => trackedPort.clientProvider[comlink__WEBPACK_IMPORTED_MODULE_7__.releaseProxy]();
|
|
19150
|
+
this.collectActiveSubscriptions();
|
|
19151
|
+
return () => trackedPort.clientProvider[comlink__WEBPACK_IMPORTED_MODULE_7__.releaseProxy]();
|
|
19152
|
+
});
|
|
19091
19153
|
}
|
|
19092
19154
|
triggerCrudUpload() {
|
|
19093
19155
|
this.withSyncImplementation(async (sync) => {
|
|
@@ -19124,10 +19186,13 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
19124
19186
|
const syncParams = this.syncParams;
|
|
19125
19187
|
// Create a new StreamingSyncImplementation for each connect call. This is usually done is all SDKs.
|
|
19126
19188
|
return new _db_sync_WebStreamingSyncImplementation__WEBPACK_IMPORTED_MODULE_3__.WebStreamingSyncImplementation({
|
|
19127
|
-
adapter: new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SqliteBucketStorage(this.
|
|
19189
|
+
adapter: new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.SqliteBucketStorage(this.distributedDB, this.logger),
|
|
19128
19190
|
remote: new _db_sync_WebRemote__WEBPACK_IMPORTED_MODULE_2__.WebRemote({
|
|
19129
19191
|
invalidateCredentials: async () => {
|
|
19130
|
-
const lastPort = this.
|
|
19192
|
+
const lastPort = this.lastWrappedPort;
|
|
19193
|
+
if (!lastPort) {
|
|
19194
|
+
throw new Error('No client port found to invalidate credentials');
|
|
19195
|
+
}
|
|
19131
19196
|
try {
|
|
19132
19197
|
this.logger.log('calling the last port client provider to invalidate credentials');
|
|
19133
19198
|
lastPort.clientProvider.invalidateCredentials();
|
|
@@ -19137,7 +19202,10 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
19137
19202
|
}
|
|
19138
19203
|
},
|
|
19139
19204
|
fetchCredentials: async () => {
|
|
19140
|
-
const lastPort = this.
|
|
19205
|
+
const lastPort = this.lastWrappedPort;
|
|
19206
|
+
if (!lastPort) {
|
|
19207
|
+
throw new Error('No client port found to fetch credentials');
|
|
19208
|
+
}
|
|
19141
19209
|
return new Promise(async (resolve, reject) => {
|
|
19142
19210
|
const abortController = new AbortController();
|
|
19143
19211
|
this.fetchCredentialsController = {
|
|
@@ -19159,7 +19227,10 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
19159
19227
|
}
|
|
19160
19228
|
}, this.logger),
|
|
19161
19229
|
uploadCrud: async () => {
|
|
19162
|
-
const lastPort = this.
|
|
19230
|
+
const lastPort = this.lastWrappedPort;
|
|
19231
|
+
if (!lastPort) {
|
|
19232
|
+
throw new Error('No client port found to upload crud');
|
|
19233
|
+
}
|
|
19163
19234
|
return new Promise(async (resolve, reject) => {
|
|
19164
19235
|
const abortController = new AbortController();
|
|
19165
19236
|
this.uploadDataController = {
|
|
@@ -19185,19 +19256,47 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
19185
19256
|
logger: this.logger
|
|
19186
19257
|
});
|
|
19187
19258
|
}
|
|
19259
|
+
/**
|
|
19260
|
+
* Opens a worker wrapped database connection. Using the last connected client port.
|
|
19261
|
+
*/
|
|
19188
19262
|
async openInternalDB() {
|
|
19189
|
-
|
|
19190
|
-
|
|
19191
|
-
|
|
19192
|
-
|
|
19193
|
-
|
|
19194
|
-
|
|
19195
|
-
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
|
|
19199
|
-
|
|
19200
|
-
|
|
19263
|
+
while (true) {
|
|
19264
|
+
try {
|
|
19265
|
+
const lastClient = this.lastWrappedPort;
|
|
19266
|
+
if (!lastClient) {
|
|
19267
|
+
// Should not really happen in practice
|
|
19268
|
+
throw new Error(`Could not open DB connection since no client is connected.`);
|
|
19269
|
+
}
|
|
19270
|
+
/**
|
|
19271
|
+
* Handle cases where the client might close while opening a connection.
|
|
19272
|
+
*/
|
|
19273
|
+
const abortController = new AbortController();
|
|
19274
|
+
const closeListener = () => {
|
|
19275
|
+
abortController.abort();
|
|
19276
|
+
};
|
|
19277
|
+
const removeCloseListener = () => {
|
|
19278
|
+
const index = lastClient.closeListeners.indexOf(closeListener);
|
|
19279
|
+
if (index >= 0) {
|
|
19280
|
+
lastClient.closeListeners.splice(index, 1);
|
|
19281
|
+
}
|
|
19282
|
+
};
|
|
19283
|
+
lastClient.closeListeners.push(closeListener);
|
|
19284
|
+
const workerPort = await withAbort(() => lastClient.clientProvider.getDBWorkerPort(), abortController.signal).catch((ex) => {
|
|
19285
|
+
removeCloseListener();
|
|
19286
|
+
throw ex;
|
|
19287
|
+
});
|
|
19288
|
+
const remote = comlink__WEBPACK_IMPORTED_MODULE_7__.wrap(workerPort);
|
|
19289
|
+
const identifier = this.syncParams.dbParams.dbFilename;
|
|
19290
|
+
/**
|
|
19291
|
+
* The open could fail if the tab is closed while we're busy opening the database.
|
|
19292
|
+
* This operation is typically executed inside an exclusive portMutex lock.
|
|
19293
|
+
* We typically execute the closeListeners using the portMutex in a different context.
|
|
19294
|
+
* We can't rely on the closeListeners to abort the operation if the tab is closed.
|
|
19295
|
+
*/
|
|
19296
|
+
const db = await withAbort(() => remote(this.syncParams.dbParams), abortController.signal).finally(() => {
|
|
19297
|
+
// We can remove the close listener here since we no longer need it past this point.
|
|
19298
|
+
removeCloseListener();
|
|
19299
|
+
});
|
|
19201
19300
|
const wrapped = new _db_adapters_WorkerWrappedAsyncDatabaseConnection__WEBPACK_IMPORTED_MODULE_5__.WorkerWrappedAsyncDatabaseConnection({
|
|
19202
19301
|
remote,
|
|
19203
19302
|
baseConnection: db,
|
|
@@ -19208,15 +19307,21 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
19208
19307
|
});
|
|
19209
19308
|
lastClient.closeListeners.push(async () => {
|
|
19210
19309
|
this.logger.info('Aborting open connection because associated tab closed.');
|
|
19310
|
+
/**
|
|
19311
|
+
* Don't await this close operation. It might never resolve if the tab is closed.
|
|
19312
|
+
* We run the close operation first, before marking the remote as closed. This gives the database some chance
|
|
19313
|
+
* to close the connection.
|
|
19314
|
+
*/
|
|
19211
19315
|
wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
|
|
19212
19316
|
wrapped.markRemoteClosed();
|
|
19213
19317
|
});
|
|
19214
19318
|
return wrapped;
|
|
19215
|
-
}
|
|
19216
|
-
|
|
19217
|
-
|
|
19218
|
-
|
|
19219
|
-
|
|
19319
|
+
}
|
|
19320
|
+
catch (ex) {
|
|
19321
|
+
this.logger.warn('Error opening internal DB', ex);
|
|
19322
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
19323
|
+
}
|
|
19324
|
+
}
|
|
19220
19325
|
}
|
|
19221
19326
|
/**
|
|
19222
19327
|
* A method to update the all shared statuses for each
|
|
@@ -19239,6 +19344,29 @@ class SharedSyncImplementation extends _powersync_common__WEBPACK_IMPORTED_MODUL
|
|
|
19239
19344
|
this.updateAllStatuses(status);
|
|
19240
19345
|
}
|
|
19241
19346
|
}
|
|
19347
|
+
/**
|
|
19348
|
+
* Runs the action with an abort controller.
|
|
19349
|
+
*/
|
|
19350
|
+
function withAbort(action, signal) {
|
|
19351
|
+
return new Promise((resolve, reject) => {
|
|
19352
|
+
if (signal.aborted) {
|
|
19353
|
+
reject(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Operation aborted by abort controller'));
|
|
19354
|
+
return;
|
|
19355
|
+
}
|
|
19356
|
+
function handleAbort() {
|
|
19357
|
+
signal.removeEventListener('abort', handleAbort);
|
|
19358
|
+
reject(new _powersync_common__WEBPACK_IMPORTED_MODULE_0__.AbortOperation('Operation aborted by abort controller'));
|
|
19359
|
+
}
|
|
19360
|
+
signal.addEventListener('abort', handleAbort, { once: true });
|
|
19361
|
+
function completePromise(action) {
|
|
19362
|
+
signal.removeEventListener('abort', handleAbort);
|
|
19363
|
+
action();
|
|
19364
|
+
}
|
|
19365
|
+
action()
|
|
19366
|
+
.then((data) => completePromise(() => resolve(data)))
|
|
19367
|
+
.catch((e) => completePromise(() => reject(e)));
|
|
19368
|
+
});
|
|
19369
|
+
}
|
|
19242
19370
|
|
|
19243
19371
|
|
|
19244
19372
|
/***/ }),
|
|
@@ -19254,8 +19382,8 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
19254
19382
|
/* harmony export */ WorkerClient: () => (/* binding */ WorkerClient)
|
|
19255
19383
|
/* harmony export */ });
|
|
19256
19384
|
/* harmony import */ var comlink__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! comlink */ "../../node_modules/comlink/dist/esm/comlink.mjs");
|
|
19257
|
-
/* harmony import */ var
|
|
19258
|
-
/* harmony import */ var
|
|
19385
|
+
/* harmony import */ var _shared_navigator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../shared/navigator */ "./lib/src/shared/navigator.js");
|
|
19386
|
+
/* harmony import */ var _SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./SharedSyncImplementation */ "./lib/src/worker/sync/SharedSyncImplementation.js");
|
|
19259
19387
|
|
|
19260
19388
|
|
|
19261
19389
|
|
|
@@ -19269,9 +19397,11 @@ class WorkerClient {
|
|
|
19269
19397
|
sync;
|
|
19270
19398
|
port;
|
|
19271
19399
|
resolvedPort = null;
|
|
19400
|
+
resolvedPortPromise = null;
|
|
19272
19401
|
constructor(sync, port) {
|
|
19273
19402
|
this.sync = sync;
|
|
19274
19403
|
this.port = port;
|
|
19404
|
+
comlink__WEBPACK_IMPORTED_MODULE_2__.expose(this, this.port);
|
|
19275
19405
|
}
|
|
19276
19406
|
async initialize() {
|
|
19277
19407
|
/**
|
|
@@ -19280,12 +19410,19 @@ class WorkerClient {
|
|
|
19280
19410
|
*/
|
|
19281
19411
|
this.port.addEventListener('message', async (event) => {
|
|
19282
19412
|
const payload = event.data;
|
|
19283
|
-
if (payload?.event ==
|
|
19413
|
+
if (payload?.event == _SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_1__.SharedSyncClientEvent.CLOSE_CLIENT) {
|
|
19284
19414
|
await this.removePort();
|
|
19285
19415
|
}
|
|
19286
19416
|
});
|
|
19287
|
-
|
|
19288
|
-
|
|
19417
|
+
/**
|
|
19418
|
+
* Keep a reference to the resolved port promise.
|
|
19419
|
+
* The init timing is difficult to predict due to the async message passing.
|
|
19420
|
+
* We only want to use a port if we are know it's been protected from being closed.
|
|
19421
|
+
* The lock based close signal will be added asynchronously. We need to use the
|
|
19422
|
+
* added port once the lock is configured.
|
|
19423
|
+
*/
|
|
19424
|
+
this.resolvedPortPromise = this.sync.addPort(this.port);
|
|
19425
|
+
this.resolvedPort = await this.resolvedPortPromise;
|
|
19289
19426
|
}
|
|
19290
19427
|
async removePort() {
|
|
19291
19428
|
if (this.resolvedPort) {
|
|
@@ -19294,7 +19431,7 @@ class WorkerClient {
|
|
|
19294
19431
|
const release = await this.sync.removePort(resolved);
|
|
19295
19432
|
this.resolvedPort = null;
|
|
19296
19433
|
this.port.postMessage({
|
|
19297
|
-
event:
|
|
19434
|
+
event: _SharedSyncImplementation__WEBPACK_IMPORTED_MODULE_1__.SharedSyncClientEvent.CLOSE_ACK,
|
|
19298
19435
|
data: {}
|
|
19299
19436
|
});
|
|
19300
19437
|
release?.();
|
|
@@ -19306,8 +19443,21 @@ class WorkerClient {
|
|
|
19306
19443
|
* When the client tab is closed, its lock will be returned. So when the shared worker attempts to acquire the lock,
|
|
19307
19444
|
* it can consider the connection to be closed.
|
|
19308
19445
|
*/
|
|
19309
|
-
addLockBasedCloseSignal(name) {
|
|
19310
|
-
(
|
|
19446
|
+
async addLockBasedCloseSignal(name) {
|
|
19447
|
+
if (!this.resolvedPortPromise) {
|
|
19448
|
+
// The init logic above is actually synchronous, so this should not happen.
|
|
19449
|
+
this.sync.broadCastLogger.warn('addLockBasedCloseSignal called before port promise registered');
|
|
19450
|
+
}
|
|
19451
|
+
else {
|
|
19452
|
+
const wrappedPort = await this.resolvedPortPromise;
|
|
19453
|
+
/**
|
|
19454
|
+
* The client registered a navigator lock. We now can guarantee detecting if the client has closed.
|
|
19455
|
+
* E.g. before this point: It's possible some ports might have been created and closed before the
|
|
19456
|
+
* lock based close signal is added. We should not trust those ports.
|
|
19457
|
+
*/
|
|
19458
|
+
wrappedPort.isProtectedFromClose = true;
|
|
19459
|
+
}
|
|
19460
|
+
(0,_shared_navigator__WEBPACK_IMPORTED_MODULE_0__.getNavigatorLocks)().request(name, async () => {
|
|
19311
19461
|
await this.removePort();
|
|
19312
19462
|
});
|
|
19313
19463
|
}
|