@powersync/web 1.36.0 → 1.37.0
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 +1127 -1235
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +481 -3111
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +688 -836
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/lib/package.json +2 -3
- package/lib/src/db/PowerSyncDatabase.d.ts +1 -2
- package/lib/src/db/PowerSyncDatabase.js +3 -4
- package/lib/src/db/adapters/AsyncWebAdapter.d.ts +40 -0
- package/lib/src/db/adapters/AsyncWebAdapter.js +69 -0
- package/lib/src/db/adapters/SSRDBAdapter.d.ts +1 -2
- package/lib/src/db/adapters/SSRDBAdapter.js +5 -6
- package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.d.ts +56 -0
- package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.js +121 -0
- package/lib/src/db/adapters/wa-sqlite/DatabaseClient.d.ts +54 -0
- package/lib/src/db/adapters/wa-sqlite/DatabaseClient.js +227 -0
- package/lib/src/db/adapters/wa-sqlite/DatabaseServer.d.ts +47 -0
- package/lib/src/db/adapters/wa-sqlite/DatabaseServer.js +146 -0
- package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.d.ts +46 -0
- package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js +147 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +14 -6
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +66 -39
- package/lib/src/db/adapters/wa-sqlite/vfs.d.ts +61 -0
- package/lib/src/db/adapters/wa-sqlite/vfs.js +91 -0
- package/lib/src/db/adapters/web-sql-flags.d.ts +5 -0
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +1 -2
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +2 -3
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +4 -19
- package/lib/src/index.d.ts +1 -4
- package/lib/src/index.js +1 -4
- package/lib/src/shared/tab_close_signal.d.ts +11 -0
- package/lib/src/shared/tab_close_signal.js +26 -0
- package/lib/src/worker/db/MultiDatabaseServer.d.ts +17 -0
- package/lib/src/worker/db/MultiDatabaseServer.js +86 -0
- package/lib/src/worker/db/WASQLiteDB.worker.js +9 -48
- package/lib/src/worker/db/open-worker-database.d.ts +3 -3
- package/lib/src/worker/db/open-worker-database.js +1 -1
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +5 -6
- package/lib/src/worker/sync/SharedSyncImplementation.js +92 -54
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -4
- package/src/db/PowerSyncDatabase.ts +3 -3
- package/src/db/adapters/AsyncWebAdapter.ts +91 -0
- package/src/db/adapters/SSRDBAdapter.ts +7 -7
- package/src/db/adapters/wa-sqlite/ConcurrentConnection.ts +137 -0
- package/src/db/adapters/wa-sqlite/DatabaseClient.ts +325 -0
- package/src/db/adapters/wa-sqlite/DatabaseServer.ts +201 -0
- package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +191 -0
- package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +87 -43
- package/src/db/adapters/wa-sqlite/vfs.ts +112 -0
- package/src/db/adapters/web-sql-flags.ts +6 -0
- package/src/db/sync/SSRWebStreamingSyncImplementation.ts +2 -3
- package/src/db/sync/SharedWebStreamingSyncImplementation.ts +4 -20
- package/src/index.ts +1 -4
- package/src/shared/tab_close_signal.ts +28 -0
- package/src/worker/db/MultiDatabaseServer.ts +104 -0
- package/src/worker/db/WASQLiteDB.worker.ts +10 -57
- package/src/worker/db/open-worker-database.ts +3 -3
- package/src/worker/sync/SharedSyncImplementation.ts +118 -58
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +0 -1881
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +0 -1
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js +0 -555
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js.map +0 -1
- package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +0 -17
- package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +0 -33
- package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +0 -49
- package/lib/src/db/adapters/AsyncDatabaseConnection.js +0 -1
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +0 -109
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +0 -404
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +0 -59
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +0 -147
- package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +0 -12
- package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +0 -19
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +0 -155
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +0 -401
- package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +0 -32
- package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +0 -49
- package/lib/src/worker/db/SharedWASQLiteConnection.d.ts +0 -42
- package/lib/src/worker/db/SharedWASQLiteConnection.js +0 -90
- package/lib/src/worker/db/WorkerWASQLiteConnection.d.ts +0 -9
- package/lib/src/worker/db/WorkerWASQLiteConnection.js +0 -12
- package/src/db/adapters/AbstractWebSQLOpenFactory.ts +0 -48
- package/src/db/adapters/AsyncDatabaseConnection.ts +0 -55
- package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +0 -489
- package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +0 -201
- package/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.ts +0 -23
- package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +0 -497
- package/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts +0 -86
- package/src/worker/db/SharedWASQLiteConnection.ts +0 -131
- package/src/worker/db/WorkerWASQLiteConnection.ts +0 -14
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List of currently tested virtual filesystems
|
|
3
|
+
*/
|
|
4
|
+
export var WASQLiteVFS;
|
|
5
|
+
(function (WASQLiteVFS) {
|
|
6
|
+
WASQLiteVFS["IDBBatchAtomicVFS"] = "IDBBatchAtomicVFS";
|
|
7
|
+
WASQLiteVFS["OPFSCoopSyncVFS"] = "OPFSCoopSyncVFS";
|
|
8
|
+
WASQLiteVFS["AccessHandlePoolVFS"] = "AccessHandlePoolVFS";
|
|
9
|
+
})(WASQLiteVFS || (WASQLiteVFS = {}));
|
|
10
|
+
export function vfsRequiresDedicatedWorkers(vfs) {
|
|
11
|
+
return vfs != WASQLiteVFS.IDBBatchAtomicVFS;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export const AsyncWASQLiteModuleFactory = async () => {
|
|
17
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite-async.mjs');
|
|
18
|
+
return factory();
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export const MultiCipherAsyncWASQLiteModuleFactory = async () => {
|
|
24
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs');
|
|
25
|
+
return factory();
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export const SyncWASQLiteModuleFactory = async () => {
|
|
31
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite.mjs');
|
|
32
|
+
return factory();
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export const MultiCipherSyncWASQLiteModuleFactory = async () => {
|
|
38
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite.mjs');
|
|
39
|
+
return factory();
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_MODULE_FACTORIES = {
|
|
45
|
+
[WASQLiteVFS.IDBBatchAtomicVFS]: async (options) => {
|
|
46
|
+
let module;
|
|
47
|
+
if (options.encryptionKey) {
|
|
48
|
+
module = await MultiCipherAsyncWASQLiteModuleFactory();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
module = await AsyncWASQLiteModuleFactory();
|
|
52
|
+
}
|
|
53
|
+
const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js');
|
|
54
|
+
return {
|
|
55
|
+
module,
|
|
56
|
+
// @ts-expect-error The types for this static method are missing upstream
|
|
57
|
+
vfs: await IDBBatchAtomicVFS.create(options.dbFileName, module, { lockPolicy: 'exclusive' })
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
[WASQLiteVFS.AccessHandlePoolVFS]: async (options) => {
|
|
61
|
+
let module;
|
|
62
|
+
if (options.encryptionKey) {
|
|
63
|
+
module = await MultiCipherSyncWASQLiteModuleFactory();
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
module = await SyncWASQLiteModuleFactory();
|
|
67
|
+
}
|
|
68
|
+
// @ts-expect-error The types for this static method are missing upstream
|
|
69
|
+
const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js');
|
|
70
|
+
return {
|
|
71
|
+
module,
|
|
72
|
+
vfs: await AccessHandlePoolVFS.create(options.dbFileName, module)
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
[WASQLiteVFS.OPFSCoopSyncVFS]: async (options) => {
|
|
76
|
+
let module;
|
|
77
|
+
if (options.encryptionKey) {
|
|
78
|
+
module = await MultiCipherSyncWASQLiteModuleFactory();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
module = await SyncWASQLiteModuleFactory();
|
|
82
|
+
}
|
|
83
|
+
// @ts-expect-error The types for this static method are missing upstream
|
|
84
|
+
const { OPFSCoopSyncVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js');
|
|
85
|
+
const vfs = await OPFSCoopSyncVFS.create(options.dbFileName, module);
|
|
86
|
+
return {
|
|
87
|
+
module,
|
|
88
|
+
vfs
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
};
|
|
@@ -62,6 +62,11 @@ export interface WebSQLOpenFactoryOptions extends SQLOpenOptions {
|
|
|
62
62
|
* or a factory method that returns a worker.
|
|
63
63
|
*/
|
|
64
64
|
worker?: string | URL | ((options: ResolvedWebSQLOpenOptions) => Worker | SharedWorker);
|
|
65
|
+
/**
|
|
66
|
+
* Use an existing port to an initialized worker.
|
|
67
|
+
* A worker will be initialized if none is provided
|
|
68
|
+
*/
|
|
69
|
+
workerPort?: MessagePort;
|
|
65
70
|
logger?: ILogger;
|
|
66
71
|
/**
|
|
67
72
|
* Where to store SQLite temporary files. Defaults to 'MEMORY'.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { AbstractStreamingSyncImplementationOptions, BaseObserver, LockOptions, PowerSyncConnectionOptions, StreamingSyncImplementation, SyncStatus, SyncStatusOptions } from '@powersync/common';
|
|
2
|
-
import { Mutex } from 'async-mutex';
|
|
1
|
+
import { AbstractStreamingSyncImplementationOptions, BaseObserver, LockOptions, Mutex, PowerSyncConnectionOptions, StreamingSyncImplementation, SyncStatus, SyncStatusOptions } from '@powersync/common';
|
|
3
2
|
export declare class SSRStreamingSyncImplementation extends BaseObserver implements StreamingSyncImplementation {
|
|
4
3
|
syncMutex: Mutex;
|
|
5
4
|
crudMutex: Mutex;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { BaseObserver, LockType, SyncStatus } from '@powersync/common';
|
|
2
|
-
import { Mutex } from 'async-mutex';
|
|
1
|
+
import { BaseObserver, LockType, Mutex, SyncStatus } from '@powersync/common';
|
|
3
2
|
export class SSRStreamingSyncImplementation extends BaseObserver {
|
|
4
3
|
syncMutex;
|
|
5
4
|
crudMutex;
|
|
@@ -15,7 +14,7 @@ export class SSRStreamingSyncImplementation extends BaseObserver {
|
|
|
15
14
|
}
|
|
16
15
|
obtainLock(lockOptions) {
|
|
17
16
|
const mutex = lockOptions.type == LockType.CRUD ? this.crudMutex : this.syncMutex;
|
|
18
|
-
return mutex.runExclusive(lockOptions.callback);
|
|
17
|
+
return mutex.runExclusive(lockOptions.callback, lockOptions.signal);
|
|
19
18
|
}
|
|
20
19
|
/**
|
|
21
20
|
* This is a no-op in SSR mode
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as Comlink from 'comlink';
|
|
2
|
-
import { getNavigatorLocks } from '../../shared/navigator.js';
|
|
3
2
|
import { AbstractSharedSyncClientProvider } from '../../worker/sync/AbstractSharedSyncClientProvider.js';
|
|
4
3
|
import { SharedSyncClientEvent } from '../../worker/sync/SharedSyncImplementation.js';
|
|
5
4
|
import { DEFAULT_CACHE_SIZE_KB, TemporaryStorageOption, resolveWebSQLFlags } from '../adapters/web-sql-flags.js';
|
|
6
5
|
import { WebStreamingSyncImplementation } from './WebStreamingSyncImplementation.js';
|
|
6
|
+
import { generateTabCloseSignal } from '../../shared/tab_close_signal.js';
|
|
7
7
|
/**
|
|
8
8
|
* The shared worker will trigger methods on this side of the message port
|
|
9
9
|
* via this client provider.
|
|
@@ -156,24 +156,9 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
156
156
|
* - We resolve the top-level promise after the lock has been registered with the shared worker.
|
|
157
157
|
* - The client sends the params to the shared worker after locks have been registered.
|
|
158
158
|
*/
|
|
159
|
-
await
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// to free resources associated with this tab.
|
|
163
|
-
// We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
|
|
164
|
-
getNavigatorLocks().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
|
|
165
|
-
if (this.abortOnClose.signal.aborted) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
// Awaiting here ensures the worker is waiting for the lock
|
|
169
|
-
await this.syncManager.addLockBasedCloseSignal(lock.name);
|
|
170
|
-
// The lock has been registered, we can continue with the initialization
|
|
171
|
-
resolve();
|
|
172
|
-
await new Promise((r) => {
|
|
173
|
-
this.abortOnClose.signal.onabort = () => r();
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
});
|
|
159
|
+
const closeSignal = await generateTabCloseSignal(this.abortOnClose.signal);
|
|
160
|
+
// Awaiting here ensures the worker is waiting for the lock
|
|
161
|
+
await this.syncManager.addLockBasedCloseSignal(closeSignal);
|
|
177
162
|
const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
|
|
178
163
|
const flags = { ...this.webOptions.flags, workers: undefined };
|
|
179
164
|
await this.syncManager.setParams({
|
package/lib/src/index.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
export * from '@powersync/common';
|
|
2
2
|
export * from './attachments/IndexDBFileSystemAdapter.js';
|
|
3
3
|
export * from './db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js';
|
|
4
|
-
export
|
|
5
|
-
export * from './db/adapters/AsyncDatabaseConnection.js';
|
|
6
|
-
export * from './db/adapters/wa-sqlite/WASQLiteConnection.js';
|
|
7
|
-
export * from './db/adapters/wa-sqlite/WASQLiteDBAdapter.js';
|
|
4
|
+
export { WASQLiteVFS } from './db/adapters/wa-sqlite/vfs.js';
|
|
8
5
|
export * from './db/adapters/wa-sqlite/WASQLiteOpenFactory.js';
|
|
9
6
|
export * from './db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js';
|
|
10
7
|
export * from './db/adapters/web-sql-flags.js';
|
package/lib/src/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
export * from '@powersync/common';
|
|
2
2
|
export * from './attachments/IndexDBFileSystemAdapter.js';
|
|
3
3
|
export * from './db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js';
|
|
4
|
-
export
|
|
5
|
-
export * from './db/adapters/AsyncDatabaseConnection.js';
|
|
6
|
-
export * from './db/adapters/wa-sqlite/WASQLiteConnection.js';
|
|
7
|
-
export * from './db/adapters/wa-sqlite/WASQLiteDBAdapter.js';
|
|
4
|
+
export { WASQLiteVFS } from './db/adapters/wa-sqlite/vfs.js';
|
|
8
5
|
export * from './db/adapters/wa-sqlite/WASQLiteOpenFactory.js';
|
|
9
6
|
export * from './db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js';
|
|
10
7
|
export * from './db/adapters/web-sql-flags.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requests a random lock that will be released once the optional signal is aborted (or, if no signal is given, when the
|
|
3
|
+
* tab is closed).
|
|
4
|
+
*
|
|
5
|
+
* This allows sending the name of the lock to another context (e.g. a shared worker), which will also attempt to
|
|
6
|
+
* acquire it. Since the lock is returned when the tab is closed, this allows the shared worker to free resources
|
|
7
|
+
* assocatiated with this tab.
|
|
8
|
+
*
|
|
9
|
+
* We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateTabCloseSignal(abort?: AbortSignal): Promise<string>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getNavigatorLocks } from './navigator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Requests a random lock that will be released once the optional signal is aborted (or, if no signal is given, when the
|
|
4
|
+
* tab is closed).
|
|
5
|
+
*
|
|
6
|
+
* This allows sending the name of the lock to another context (e.g. a shared worker), which will also attempt to
|
|
7
|
+
* acquire it. Since the lock is returned when the tab is closed, this allows the shared worker to free resources
|
|
8
|
+
* assocatiated with this tab.
|
|
9
|
+
*
|
|
10
|
+
* We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
|
|
11
|
+
*/
|
|
12
|
+
export function generateTabCloseSignal(abort) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const options = { signal: abort };
|
|
15
|
+
getNavigatorLocks()
|
|
16
|
+
.request(`tab-close-signal-${crypto.randomUUID()}`, options, (lock) => {
|
|
17
|
+
resolve(lock.name);
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
if (abort) {
|
|
20
|
+
abort.addEventListener('abort', () => resolve());
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
})
|
|
24
|
+
.catch(reject);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ILogger } from '@powersync/common';
|
|
2
|
+
import { ClientConnectionView } from '../../db/adapters/wa-sqlite/DatabaseServer.js';
|
|
3
|
+
import { ResolvedWASQLiteOpenFactoryOptions, WorkerDBOpenerOptions } from '../../db/adapters/wa-sqlite/WASQLiteOpenFactory.js';
|
|
4
|
+
/**
|
|
5
|
+
* Shared state to manage multiple database connections hosted by a worker.
|
|
6
|
+
*/
|
|
7
|
+
export declare class MultiDatabaseServer {
|
|
8
|
+
readonly logger: ILogger;
|
|
9
|
+
private activeDatabases;
|
|
10
|
+
constructor(logger: ILogger);
|
|
11
|
+
handleConnection(options: WorkerDBOpenerOptions): Promise<ClientConnectionView>;
|
|
12
|
+
connectToExisting(name: string, lockName: string): Promise<ClientConnectionView>;
|
|
13
|
+
openConnectionLocally(options: ResolvedWASQLiteOpenFactoryOptions, lockName?: string): Promise<ClientConnectionView>;
|
|
14
|
+
private databaseOpenAttempt;
|
|
15
|
+
closeAll(): Promise<void[]>;
|
|
16
|
+
}
|
|
17
|
+
export declare const isSharedWorker: boolean;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as Comlink from 'comlink';
|
|
2
|
+
import { DatabaseServer } from '../../db/adapters/wa-sqlite/DatabaseServer.js';
|
|
3
|
+
import { getNavigatorLocks } from '../../shared/navigator.js';
|
|
4
|
+
import { RawSqliteConnection } from '../../db/adapters/wa-sqlite/RawSqliteConnection.js';
|
|
5
|
+
import { ConcurrentSqliteConnection } from '../../db/adapters/wa-sqlite/ConcurrentConnection.js';
|
|
6
|
+
const OPEN_DB_LOCK = 'open-wasqlite-db';
|
|
7
|
+
/**
|
|
8
|
+
* Shared state to manage multiple database connections hosted by a worker.
|
|
9
|
+
*/
|
|
10
|
+
export class MultiDatabaseServer {
|
|
11
|
+
logger;
|
|
12
|
+
activeDatabases = new Map();
|
|
13
|
+
constructor(logger) {
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
async handleConnection(options) {
|
|
17
|
+
this.logger.setLevel(options.logLevel);
|
|
18
|
+
return Comlink.proxy(await this.openConnectionLocally(options, options.lockName));
|
|
19
|
+
}
|
|
20
|
+
async connectToExisting(name, lockName) {
|
|
21
|
+
return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
|
|
22
|
+
const server = this.activeDatabases.get(name);
|
|
23
|
+
if (server == null) {
|
|
24
|
+
throw new Error(`connectToExisting(${name}) failed because the worker doesn't own a database with that name.`);
|
|
25
|
+
}
|
|
26
|
+
return Comlink.proxy(await server.connect(lockName));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async openConnectionLocally(options, lockName) {
|
|
30
|
+
// Especially on Firefox, we're sometimes seeing "NoModificationAllowedError"s when opening OPFS databases we can
|
|
31
|
+
// work around by retrying.
|
|
32
|
+
const maxAttempts = 3;
|
|
33
|
+
let server;
|
|
34
|
+
for (let count = 0; count < maxAttempts - 1; count++) {
|
|
35
|
+
try {
|
|
36
|
+
server = await this.databaseOpenAttempt(options);
|
|
37
|
+
}
|
|
38
|
+
catch (ex) {
|
|
39
|
+
this.logger.warn(`Attempt ${count + 1} of ${maxAttempts} to open database failed, retrying in 1 second...`, ex);
|
|
40
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Final attempt if we haven't been able to open the server - rethrow errors if we still can't open.
|
|
44
|
+
server ??= await this.databaseOpenAttempt(options);
|
|
45
|
+
return server.connect(lockName);
|
|
46
|
+
}
|
|
47
|
+
async databaseOpenAttempt(options) {
|
|
48
|
+
return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
|
|
49
|
+
const { dbFilename } = options;
|
|
50
|
+
let server = this.activeDatabases.get(dbFilename);
|
|
51
|
+
if (server == null) {
|
|
52
|
+
const needsNavigatorLocks = !isSharedWorker;
|
|
53
|
+
const connection = new RawSqliteConnection(options);
|
|
54
|
+
const withSafeConcurrency = new ConcurrentSqliteConnection(connection, needsNavigatorLocks);
|
|
55
|
+
// Initializing the RawSqliteConnection will run some pragmas that might write to the database file, so we want
|
|
56
|
+
// to do that in an exclusive lock. Note that OPEN_DB_LOCK is not enough for that, as another tab might have
|
|
57
|
+
// already created a connection (and is thus outside of OPEN_DB_LOCK) while currently writing to it.
|
|
58
|
+
const returnLease = await withSafeConcurrency.acquireMutex();
|
|
59
|
+
try {
|
|
60
|
+
await connection.init();
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
returnLease();
|
|
64
|
+
await connection.close();
|
|
65
|
+
throw e;
|
|
66
|
+
}
|
|
67
|
+
returnLease();
|
|
68
|
+
const onClose = () => this.activeDatabases.delete(dbFilename);
|
|
69
|
+
server = new DatabaseServer({
|
|
70
|
+
inner: withSafeConcurrency,
|
|
71
|
+
logger: this.logger,
|
|
72
|
+
onClose
|
|
73
|
+
});
|
|
74
|
+
this.activeDatabases.set(dbFilename, server);
|
|
75
|
+
}
|
|
76
|
+
return server;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
closeAll() {
|
|
80
|
+
const existingDatabases = [...this.activeDatabases.values()];
|
|
81
|
+
return Promise.all(existingDatabases.map((db) => {
|
|
82
|
+
db.forceClose();
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export const isSharedWorker = 'SharedWorkerGlobalScope' in globalThis;
|
|
@@ -4,66 +4,27 @@
|
|
|
4
4
|
import '@journeyapps/wa-sqlite';
|
|
5
5
|
import { createBaseLogger, createLogger } from '@powersync/common';
|
|
6
6
|
import * as Comlink from 'comlink';
|
|
7
|
-
import {
|
|
8
|
-
import { SharedWASQLiteConnection } from './SharedWASQLiteConnection.js';
|
|
9
|
-
import { WorkerWASQLiteConnection } from './WorkerWASQLiteConnection.js';
|
|
7
|
+
import { isSharedWorker, MultiDatabaseServer } from './MultiDatabaseServer.js';
|
|
10
8
|
const baseLogger = createBaseLogger();
|
|
11
9
|
baseLogger.useDefaults();
|
|
12
10
|
const logger = createLogger('db-worker');
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Prevent multiple simultaneous opens from causing race conditions
|
|
18
|
-
return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
|
|
19
|
-
const clientId = nextClientId++;
|
|
20
|
-
const { dbFilename, logLevel } = options;
|
|
21
|
-
logger.setLevel(logLevel);
|
|
22
|
-
if (!DBMap.has(dbFilename)) {
|
|
23
|
-
const clientIds = new Set();
|
|
24
|
-
// This format returns proxy objects for function callbacks
|
|
25
|
-
const connection = new WorkerWASQLiteConnection(options);
|
|
26
|
-
await connection.init();
|
|
27
|
-
connection.registerListener({
|
|
28
|
-
holdOverwritten: async () => {
|
|
29
|
-
/**
|
|
30
|
-
* The previous hold has been overwritten, without being released.
|
|
31
|
-
* we need to cleanup any resources associated with it.
|
|
32
|
-
* We can perform a rollback to release any potential transactions that were started.
|
|
33
|
-
*/
|
|
34
|
-
await connection.execute('ROLLBACK').catch(() => { });
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
DBMap.set(dbFilename, {
|
|
38
|
-
clientIds,
|
|
39
|
-
db: connection
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
// Associates this clientId with the shared connection entry
|
|
43
|
-
const sharedConnection = new SharedWASQLiteConnection({
|
|
44
|
-
dbMap: DBMap,
|
|
45
|
-
dbFilename,
|
|
46
|
-
clientId,
|
|
47
|
-
logger
|
|
48
|
-
});
|
|
49
|
-
return Comlink.proxy(sharedConnection);
|
|
50
|
-
});
|
|
11
|
+
const server = new MultiDatabaseServer(logger);
|
|
12
|
+
const exposedFunctions = {
|
|
13
|
+
connect: (config) => server.handleConnection(config),
|
|
14
|
+
connectToExisting: ({ identifier, lockName }) => server.connectToExisting(identifier, lockName)
|
|
51
15
|
};
|
|
52
16
|
// Check if we're in a SharedWorker context
|
|
53
|
-
if (
|
|
17
|
+
if (isSharedWorker) {
|
|
54
18
|
const _self = self;
|
|
55
19
|
_self.onconnect = function (event) {
|
|
56
20
|
const port = event.ports[0];
|
|
57
|
-
Comlink.expose(
|
|
21
|
+
Comlink.expose(exposedFunctions, port);
|
|
58
22
|
};
|
|
59
23
|
}
|
|
60
24
|
else {
|
|
61
25
|
// A dedicated worker can be shared externally
|
|
62
|
-
Comlink.expose(
|
|
26
|
+
Comlink.expose(exposedFunctions);
|
|
63
27
|
}
|
|
64
28
|
addEventListener('unload', () => {
|
|
65
|
-
|
|
66
|
-
const { db } = dbConnection;
|
|
67
|
-
db.close?.();
|
|
68
|
-
});
|
|
29
|
+
server.closeAll();
|
|
69
30
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Comlink from 'comlink';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/vfs.js';
|
|
3
|
+
import { OpenWorkerConnection } from '../../db/adapters/wa-sqlite/DatabaseClient.js';
|
|
4
4
|
/**
|
|
5
5
|
* Opens a shared or dedicated worker which exposes opening of database connections
|
|
6
6
|
*/
|
|
@@ -9,6 +9,6 @@ export declare function openWorkerDatabasePort(workerIdentifier: string, multipl
|
|
|
9
9
|
* @returns A function which allows for opening database connections inside
|
|
10
10
|
* a worker.
|
|
11
11
|
*/
|
|
12
|
-
export declare function getWorkerDatabaseOpener(workerIdentifier: string, multipleTabs?: boolean, worker?: string | URL): Comlink.Remote<
|
|
12
|
+
export declare function getWorkerDatabaseOpener(workerIdentifier: string, multipleTabs?: boolean, worker?: string | URL): Comlink.Remote<OpenWorkerConnection>;
|
|
13
13
|
export declare function resolveWorkerDatabasePortFactory(worker: () => Worker | SharedWorker): Worker | MessagePort;
|
|
14
14
|
export declare function isSharedWorker(worker: Worker | SharedWorker): worker is SharedWorker;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Comlink from 'comlink';
|
|
2
|
-
import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/
|
|
2
|
+
import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/vfs.js';
|
|
3
3
|
/**
|
|
4
4
|
* Opens a shared or dedicated worker which exposes opening of database connections
|
|
5
5
|
*/
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { BaseObserver, ConnectionManager, DBAdapter, SubscribedStream, SyncStatus, type ILogLevel, type ILogger, type PowerSyncConnectionOptions, type StreamingSyncImplementation, type StreamingSyncImplementationListener, type SyncStatusOptions } from '@powersync/common';
|
|
2
|
-
import { Mutex } from 'async-mutex';
|
|
1
|
+
import { BaseObserver, ConnectionManager, DBAdapter, SubscribedStream, SyncStatus, Mutex, type ILogLevel, type ILogger, type PowerSyncConnectionOptions, type StreamingSyncImplementation, type StreamingSyncImplementationListener, type SyncStatusOptions } from '@powersync/common';
|
|
3
2
|
import * as Comlink from 'comlink';
|
|
4
3
|
import { WebStreamingSyncImplementation, WebStreamingSyncImplementationOptions } from '../../db/sync/WebStreamingSyncImplementation.js';
|
|
5
|
-
import { WorkerWrappedAsyncDatabaseConnection } from '../../db/adapters/WorkerWrappedAsyncDatabaseConnection.js';
|
|
6
4
|
import { ResolvedWebSQLOpenOptions } from '../../db/adapters/web-sql-flags.js';
|
|
7
5
|
import { AbstractSharedSyncClientProvider } from './AbstractSharedSyncClientProvider.js';
|
|
8
6
|
/**
|
|
@@ -73,7 +71,7 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
|
|
|
73
71
|
protected connectionManager: ConnectionManager;
|
|
74
72
|
syncStatus: SyncStatus;
|
|
75
73
|
broadCastLogger: ILogger;
|
|
76
|
-
protected
|
|
74
|
+
protected readonly database: DBAdapter;
|
|
77
75
|
constructor();
|
|
78
76
|
get lastSyncedAt(): Date | undefined;
|
|
79
77
|
get isConnected(): boolean;
|
|
@@ -126,9 +124,10 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
|
|
|
126
124
|
protected withSyncImplementation<T>(callback: (sync: StreamingSyncImplementation) => Promise<T>): Promise<T>;
|
|
127
125
|
protected generateStreamingImplementation(): WebStreamingSyncImplementation;
|
|
128
126
|
/**
|
|
129
|
-
*
|
|
127
|
+
* Requests a random client to share its database connection with us.
|
|
130
128
|
*/
|
|
131
|
-
|
|
129
|
+
private openInternalDB;
|
|
130
|
+
private generateReconnectableDatabase;
|
|
132
131
|
/**
|
|
133
132
|
* A method to update the all shared statuses for each
|
|
134
133
|
* client.
|