@powersync/web 0.0.0-dev-20251201150812 → 0.0.0-dev-20251203144301
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 +2415 -58
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +1884 -39
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +1809 -8
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js +0 -1203
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js +0 -1203
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_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 +52 -28
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +3 -3
- 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/db/WASQLiteDB.worker.js +0 -1
- package/lib/src/worker/db/opfs.d.ts +96 -0
- package/lib/src/worker/db/opfs.js +582 -0
- package/lib/src/worker/sync/SharedSyncImplementation.js +23 -4
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +71 -48
- package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +3 -4
- package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +3 -3
- package/src/worker/db/WASQLiteDB.worker.ts +0 -2
- package/src/worker/db/opfs.ts +623 -0
- package/src/worker/sync/SharedSyncImplementation.ts +29 -8
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js +0 -1813
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js.map +0 -1
- 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
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
createLogger,
|
|
11
11
|
type ILogger
|
|
12
12
|
} from '@powersync/common';
|
|
13
|
-
import { getNavigatorLocks } from '
|
|
13
|
+
import { getNavigatorLocks } from '../../shared/navigator';
|
|
14
14
|
import { AsyncDatabaseConnection, ConnectionClosedError } from './AsyncDatabaseConnection';
|
|
15
15
|
import { SharedConnectionWorker, WebDBAdapter } from './WebDBAdapter';
|
|
16
16
|
import { WorkerWrappedAsyncDatabaseConnection } from './WorkerWrappedAsyncDatabaseConnection';
|
|
@@ -113,23 +113,29 @@ export class LockedAsyncDatabaseAdapter
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
protected async openInternalDB() {
|
|
116
|
-
// Dispose any previous table change listener.
|
|
117
|
-
this._disposeTableChangeListener?.();
|
|
118
|
-
this._disposeTableChangeListener = null;
|
|
119
|
-
|
|
120
|
-
const isReOpen = !!this._db;
|
|
121
|
-
|
|
122
|
-
this._db = await this.options.openConnection();
|
|
123
|
-
await this._db.init();
|
|
124
|
-
this._config = await this._db.getConfig();
|
|
125
|
-
await this.registerOnChangeListener(this._db);
|
|
126
|
-
if (isReOpen) {
|
|
127
|
-
this.iterateListeners((cb) => cb.databaseReOpened?.());
|
|
128
|
-
}
|
|
129
116
|
/**
|
|
130
|
-
*
|
|
117
|
+
* Execute opening of the db in a lock in order not to interfere with other operations.
|
|
131
118
|
*/
|
|
132
|
-
this.
|
|
119
|
+
return this._acquireLock(async () => {
|
|
120
|
+
// Dispose any previous table change listener.
|
|
121
|
+
this._disposeTableChangeListener?.();
|
|
122
|
+
this._disposeTableChangeListener = null;
|
|
123
|
+
this._db?.close().catch((ex) => this.logger.warn(`Error closing database before opening new instance`, ex));
|
|
124
|
+
const isReOpen = !!this._db;
|
|
125
|
+
this._db = null;
|
|
126
|
+
|
|
127
|
+
this._db = await this.options.openConnection();
|
|
128
|
+
await this._db.init();
|
|
129
|
+
this._config = await this._db.getConfig();
|
|
130
|
+
await this.registerOnChangeListener(this._db);
|
|
131
|
+
if (isReOpen) {
|
|
132
|
+
this.iterateListeners((cb) => cb.databaseReOpened?.());
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* This is only required for the long-lived shared IndexedDB connections.
|
|
136
|
+
*/
|
|
137
|
+
this.requiresHolds = (this._config as ResolvedWASQLiteOpenFactoryOptions).vfs == WASQLiteVFS.IDBBatchAtomicVFS;
|
|
138
|
+
});
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
protected _reOpen() {
|
|
@@ -154,7 +160,23 @@ export class LockedAsyncDatabaseAdapter
|
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
protected async _init() {
|
|
157
|
-
|
|
163
|
+
/**
|
|
164
|
+
* For OPFS, we can see this open call sometimes fail due to NoModificationAllowedError.
|
|
165
|
+
* We should be able to recover from this by re-opening the database.
|
|
166
|
+
*/
|
|
167
|
+
const maxAttempts = 3;
|
|
168
|
+
for (let count = 0; count < maxAttempts; count++) {
|
|
169
|
+
try {
|
|
170
|
+
await this.openInternalDB();
|
|
171
|
+
break;
|
|
172
|
+
} catch (ex) {
|
|
173
|
+
if (count == maxAttempts - 1) {
|
|
174
|
+
throw ex;
|
|
175
|
+
}
|
|
176
|
+
this.logger.warn(`Attempt ${count + 1} of ${maxAttempts} to open database failed, retrying in 1 second...`, ex);
|
|
177
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
158
180
|
this.iterateListeners((cb) => cb.initialized?.());
|
|
159
181
|
}
|
|
160
182
|
|
|
@@ -252,13 +274,10 @@ export class LockedAsyncDatabaseAdapter
|
|
|
252
274
|
);
|
|
253
275
|
}
|
|
254
276
|
|
|
255
|
-
protected async
|
|
256
|
-
await this.waitForInitialized();
|
|
257
|
-
|
|
277
|
+
protected async _acquireLock(callback: () => Promise<any>, options?: { timeoutMs?: number }): Promise<any> {
|
|
258
278
|
if (this.closing) {
|
|
259
279
|
throw new Error(`Cannot acquire lock, the database is closing`);
|
|
260
280
|
}
|
|
261
|
-
|
|
262
281
|
const abortController = new AbortController();
|
|
263
282
|
this.pendingAbortControllers.add(abortController);
|
|
264
283
|
const { timeoutMs } = options ?? {};
|
|
@@ -278,37 +297,41 @@ export class LockedAsyncDatabaseAdapter
|
|
|
278
297
|
if (timeoutId) {
|
|
279
298
|
clearTimeout(timeoutId);
|
|
280
299
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
try {
|
|
286
|
-
await this.databaseOpenPromise;
|
|
287
|
-
} catch (ex) {
|
|
288
|
-
// This will cause a retry of opening the database.
|
|
289
|
-
const wrappedError = new ConnectionClosedError('Could not open database');
|
|
290
|
-
wrappedError.cause = ex;
|
|
291
|
-
throw wrappedError;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
300
|
+
return await callback();
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
}
|
|
294
304
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
305
|
+
protected async acquireLock(callback: () => Promise<any>, options?: { timeoutMs?: number }): Promise<any> {
|
|
306
|
+
await this.waitForInitialized();
|
|
307
|
+
|
|
308
|
+
return this._acquireLock(async () => {
|
|
309
|
+
let holdId: string | null = null;
|
|
310
|
+
try {
|
|
311
|
+
// The database is being opened in the background. Wait for it here.
|
|
312
|
+
if (this.databaseOpenPromise) {
|
|
313
|
+
/**
|
|
314
|
+
* We can't await this since it uses the same lock as we're in now.
|
|
315
|
+
*/
|
|
316
|
+
throw new ConnectionClosedError('Connection is busy re-opening');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
|
|
320
|
+
return await callback();
|
|
321
|
+
} catch (ex) {
|
|
322
|
+
if (ex instanceof ConnectionClosedError || (ex instanceof Error && ex.name === 'NoModificationAllowedError')) {
|
|
323
|
+
if (this.options.reOpenOnConnectionClosed && !this.databaseOpenPromise && !this.closing) {
|
|
324
|
+
// Immediately re-open the database. We need to miss as little table updates as possible.
|
|
325
|
+
this.reOpenInternalDB();
|
|
308
326
|
}
|
|
309
327
|
}
|
|
328
|
+
throw ex;
|
|
329
|
+
} finally {
|
|
330
|
+
if (holdId) {
|
|
331
|
+
await this.baseDB.releaseHold(holdId);
|
|
332
|
+
}
|
|
310
333
|
}
|
|
311
|
-
);
|
|
334
|
+
}, options);
|
|
312
335
|
}
|
|
313
336
|
|
|
314
337
|
async readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T> {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as SQLite from '@journeyapps/wa-sqlite';
|
|
2
2
|
import { BaseObserver, BatchedUpdateNotification } from '@powersync/common';
|
|
3
3
|
import { Mutex } from 'async-mutex';
|
|
4
|
+
import { OPFSCoopSyncVFS } from '../../../worker/db/opfs';
|
|
4
5
|
import { AsyncDatabaseConnection, OnTableChangeCallback, ProxiedQueryResult } from '../AsyncDatabaseConnection';
|
|
5
6
|
import { ResolvedWASQLiteOpenFactoryOptions } from './WASQLiteOpenFactory';
|
|
6
|
-
|
|
7
7
|
/**
|
|
8
8
|
* List of currently tested virtual filesystems
|
|
9
9
|
*/
|
|
@@ -124,11 +124,10 @@ export const DEFAULT_MODULE_FACTORIES = {
|
|
|
124
124
|
} else {
|
|
125
125
|
module = await SyncWASQLiteModuleFactory();
|
|
126
126
|
}
|
|
127
|
-
|
|
128
|
-
const { OPFSCoopSyncVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js');
|
|
127
|
+
const vfs = await OPFSCoopSyncVFS.create(options.dbFileName, module);
|
|
129
128
|
return {
|
|
130
129
|
module,
|
|
131
|
-
vfs:
|
|
130
|
+
vfs: vfs as any
|
|
132
131
|
};
|
|
133
132
|
}
|
|
134
133
|
};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { type ILogLevel
|
|
1
|
+
import { DBAdapter, type ILogLevel } from '@powersync/common';
|
|
2
2
|
import * as Comlink from 'comlink';
|
|
3
3
|
import { openWorkerDatabasePort, resolveWorkerDatabasePortFactory } from '../../../worker/db/open-worker-database';
|
|
4
4
|
import { AbstractWebSQLOpenFactory } from '../AbstractWebSQLOpenFactory';
|
|
5
5
|
import { AsyncDatabaseConnection, OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection';
|
|
6
6
|
import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter';
|
|
7
|
+
import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection';
|
|
7
8
|
import {
|
|
8
9
|
DEFAULT_CACHE_SIZE_KB,
|
|
9
10
|
ResolvedWebSQLOpenOptions,
|
|
10
11
|
TemporaryStorageOption,
|
|
11
12
|
WebSQLOpenFactoryOptions
|
|
12
13
|
} from '../web-sql-flags';
|
|
13
|
-
import {
|
|
14
|
-
import { WASqliteConnection, WASQLiteVFS } from './WASQLiteConnection';
|
|
14
|
+
import { WASQLiteVFS, WASqliteConnection } from './WASQLiteConnection';
|
|
15
15
|
|
|
16
16
|
export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions {
|
|
17
17
|
vfs?: WASQLiteVFS;
|
|
@@ -17,11 +17,9 @@ const logger = createLogger('db-worker');
|
|
|
17
17
|
|
|
18
18
|
const DBMap = new Map<string, SharedDBWorkerConnection>();
|
|
19
19
|
const OPEN_DB_LOCK = 'open-wasqlite-db';
|
|
20
|
-
|
|
21
20
|
let nextClientId = 1;
|
|
22
21
|
|
|
23
22
|
const openDBShared = async (options: WorkerDBOpenerOptions): Promise<AsyncDatabaseConnection> => {
|
|
24
|
-
// Prevent multiple simultaneous opens from causing race conditions
|
|
25
23
|
return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
|
|
26
24
|
const clientId = nextClientId++;
|
|
27
25
|
const { dbFilename, logLevel } = options;
|