@powersync/web 0.0.0-dev-20260311103504 → 0.0.0-dev-20260503073249
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/2075a31bb151adbb9767.wasm +0 -0
- package/dist/3322bc84de986b63c2cd.wasm +0 -0
- package/dist/8e97452e297be23b5e50.wasm +0 -0
- package/dist/fbc178b70d530e8ce02b.wasm +0 -0
- package/dist/index.umd.js +5341 -1279
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +1113 -3526
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +1397 -1332
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-9af0a7.umd.js +31 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-9af0a7.umd.js.map +1 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-bbf5a9.umd.js +31 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-bbf5a9.umd.js.map +1 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-c26e0f.umd.js +31 -0
- package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-cc5fcc.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-c26e0f.umd.js.map} +1 -1
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +31 -0
- package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map} +1 -1
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-2fb422.umd.js +3562 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-2fb422.umd.js.map +1 -0
- package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-0df390.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-96fb23.umd.js} +16 -16
- package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-0df390.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-96fb23.umd.js.map} +1 -1
- package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-151024.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-c89911.umd.js} +12 -12
- package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-151024.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-c89911.umd.js.map} +1 -1
- package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-c01ef0.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-ec4eb1.umd.js} +14 -14
- package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-c01ef0.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-ec4eb1.umd.js.map} +1 -1
- package/lib/package.json +4 -5
- package/lib/src/db/PowerSyncDatabase.d.ts +2 -3
- package/lib/src/db/PowerSyncDatabase.js +3 -12
- package/lib/src/db/adapters/AsyncWebAdapter.d.ts +50 -0
- package/lib/src/db/adapters/AsyncWebAdapter.js +163 -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 +145 -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 +28 -6
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +104 -55
- package/lib/src/db/adapters/wa-sqlite/vfs.d.ts +50 -0
- package/lib/src/db/adapters/wa-sqlite/vfs.js +76 -0
- package/lib/src/db/adapters/web-sql-flags.d.ts +5 -0
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +5 -2
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +6 -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 +89 -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 +2 -2
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +5 -6
- package/lib/src/worker/sync/SharedSyncImplementation.js +88 -54
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -6
- package/src/db/PowerSyncDatabase.ts +4 -12
- package/src/db/adapters/AsyncWebAdapter.ts +207 -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 +203 -0
- package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +194 -0
- package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +152 -63
- package/src/db/adapters/wa-sqlite/vfs.ts +96 -0
- package/src/db/adapters/web-sql-flags.ts +6 -0
- package/src/db/sync/SSRWebStreamingSyncImplementation.ts +7 -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 +107 -0
- package/src/worker/db/WASQLiteDB.worker.ts +10 -57
- package/src/worker/db/open-worker-database.ts +4 -4
- package/src/worker/sync/SharedSyncImplementation.ts +114 -58
- package/dist/26d61ca9f5694d064635.wasm +0 -0
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +0 -1878
- 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/dist/b4c6283dc473b6b3fd24.wasm +0 -0
- package/dist/c78985091a0b22aaef03.wasm +0 -0
- package/dist/ca59e199e1138b553fad.wasm +0 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-b9c070.umd.js +0 -31
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-b9c070.umd.js.map +0 -1
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-c99c07.umd.js +0 -31
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-c99c07.umd.js.map +0 -1
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-cc5fcc.umd.js +0 -31
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +0 -31
- 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 -401
- 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 -490
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/web",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20260503073249",
|
|
4
4
|
"description": "PowerSync Web SDK",
|
|
5
5
|
"main": "lib/src/index.js",
|
|
6
6
|
"module": "lib/src/index.js",
|
|
@@ -52,21 +52,20 @@
|
|
|
52
52
|
"real-time data stream",
|
|
53
53
|
"live data"
|
|
54
54
|
],
|
|
55
|
-
"author": "
|
|
55
|
+
"author": "PowerSync",
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@journeyapps/wa-sqlite": "^1.5.0",
|
|
59
|
-
"@powersync/common": "0.0.0-dev-
|
|
59
|
+
"@powersync/common": "0.0.0-dev-20260503073249"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"async-mutex": "^0.5.0",
|
|
63
62
|
"bson": "^6.10.4",
|
|
64
63
|
"comlink": "^4.4.2",
|
|
65
64
|
"commander": "^12.1.0",
|
|
66
|
-
"@powersync/common": "0.0.0-dev-
|
|
65
|
+
"@powersync/common": "0.0.0-dev-20260503073249"
|
|
67
66
|
},
|
|
68
67
|
"devDependencies": {
|
|
69
|
-
"@journeyapps/wa-sqlite": "^1.
|
|
68
|
+
"@journeyapps/wa-sqlite": "^1.7.0",
|
|
70
69
|
"@types/uuid": "^9.0.6",
|
|
71
70
|
"crypto-browserify": "^3.12.0",
|
|
72
71
|
"glob": "^11.0.0",
|
|
@@ -10,15 +10,14 @@ import {
|
|
|
10
10
|
TriggerManagerConfig,
|
|
11
11
|
isDBAdapter,
|
|
12
12
|
isSQLOpenFactory,
|
|
13
|
+
Mutex,
|
|
13
14
|
type BucketStorageAdapter,
|
|
14
15
|
type PowerSyncBackendConnector,
|
|
15
16
|
type PowerSyncCloseOptions,
|
|
16
17
|
type RequiredAdditionalConnectionOptions
|
|
17
18
|
} from '@powersync/common';
|
|
18
|
-
import { Mutex } from 'async-mutex';
|
|
19
19
|
import { getNavigatorLocks } from '../shared/navigator.js';
|
|
20
20
|
import { NAVIGATOR_TRIGGER_CLAIM_MANAGER } from './NavigatorTriggerClaimManager.js';
|
|
21
|
-
import { LockedAsyncDatabaseAdapter } from './adapters/LockedAsyncDatabaseAdapter.js';
|
|
22
21
|
import { WebDBAdapter } from './adapters/WebDBAdapter.js';
|
|
23
22
|
import { WASQLiteOpenFactory } from './adapters/wa-sqlite/WASQLiteOpenFactory.js';
|
|
24
23
|
import {
|
|
@@ -35,9 +34,11 @@ import {
|
|
|
35
34
|
WebStreamingSyncImplementation,
|
|
36
35
|
WebStreamingSyncImplementationOptions
|
|
37
36
|
} from './sync/WebStreamingSyncImplementation.js';
|
|
37
|
+
import { AsyncDbAdapter } from './adapters/AsyncWebAdapter.js';
|
|
38
38
|
|
|
39
39
|
export interface WebPowerSyncFlags extends WebSQLFlags {
|
|
40
40
|
/**
|
|
41
|
+
* @deprecated This flag is no longer used. Navigator locks now handle tab detection automatically.
|
|
41
42
|
* Externally unload open PowerSync database instances when the window closes.
|
|
42
43
|
* Setting this to `true` requires calling `close` on all open PowerSyncDatabase
|
|
43
44
|
* instances before the window unloads
|
|
@@ -127,7 +128,6 @@ function assertValidDatabaseOptions(options: WebPowerSyncDatabaseOptions): void
|
|
|
127
128
|
export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
128
129
|
static SHARED_MUTEX = new Mutex();
|
|
129
130
|
|
|
130
|
-
protected unloadListener?: () => Promise<void>;
|
|
131
131
|
protected resolvedFlags: WebPowerSyncFlags;
|
|
132
132
|
|
|
133
133
|
constructor(options: WebPowerSyncDatabaseOptionsWithAdapter);
|
|
@@ -140,15 +140,10 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
|
140
140
|
assertValidDatabaseOptions(options);
|
|
141
141
|
|
|
142
142
|
this.resolvedFlags = resolveWebPowerSyncFlags(options.flags);
|
|
143
|
-
|
|
144
|
-
if (this.resolvedFlags.enableMultiTabs && !this.resolvedFlags.externallyUnload) {
|
|
145
|
-
this.unloadListener = () => this.close({ disconnect: false });
|
|
146
|
-
window.addEventListener('unload', this.unloadListener);
|
|
147
|
-
}
|
|
148
143
|
}
|
|
149
144
|
|
|
150
145
|
async _initialize(): Promise<void> {
|
|
151
|
-
if (this.database instanceof
|
|
146
|
+
if (this.database instanceof AsyncDbAdapter) {
|
|
152
147
|
/**
|
|
153
148
|
* While init is done automatically,
|
|
154
149
|
* LockedAsyncDatabaseAdapter only exposes config after init.
|
|
@@ -190,9 +185,6 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
|
190
185
|
* multiple tabs are not enabled.
|
|
191
186
|
*/
|
|
192
187
|
close(options?: PowerSyncCloseOptions): Promise<void> {
|
|
193
|
-
if (this.unloadListener) {
|
|
194
|
-
window.removeEventListener('unload', this.unloadListener);
|
|
195
|
-
}
|
|
196
188
|
return super.close({
|
|
197
189
|
// Don't disconnect by default if multiple tabs are enabled
|
|
198
190
|
disconnect: options?.disconnect ?? !this.resolvedFlags.enableMultiTabs
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConnectionPool,
|
|
3
|
+
DBAdapterDefaultMixin,
|
|
4
|
+
DBAdapterListener,
|
|
5
|
+
DBLockOptions,
|
|
6
|
+
LockContext,
|
|
7
|
+
Mutex,
|
|
8
|
+
Semaphore,
|
|
9
|
+
UnlockFn
|
|
10
|
+
} from '@powersync/common';
|
|
11
|
+
import { SharedConnectionWorker, WebDBAdapter, WebDBAdapterConfiguration } from './WebDBAdapter.js';
|
|
12
|
+
import { DatabaseClient } from './wa-sqlite/DatabaseClient.js';
|
|
13
|
+
|
|
14
|
+
type PendingListener = { listener: Partial<DBAdapterListener>; closeAfterRegisteredOnResolvedPool?: () => void };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A connection pool implementation delegating to another pool opened asynchronnously.
|
|
18
|
+
*/
|
|
19
|
+
class AsyncConnectionPool implements ConnectionPool {
|
|
20
|
+
protected readonly state: Promise<PoolState>;
|
|
21
|
+
protected resolvedWriter?: DatabaseClient;
|
|
22
|
+
|
|
23
|
+
private readonly pendingListeners = new Set<PendingListener>();
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
inner: Promise<PoolConnection>,
|
|
27
|
+
readonly name: string
|
|
28
|
+
) {
|
|
29
|
+
this.state = inner.then((client) => {
|
|
30
|
+
for (const pending of this.pendingListeners) {
|
|
31
|
+
pending.closeAfterRegisteredOnResolvedPool = client.writer.registerListener(pending.listener);
|
|
32
|
+
}
|
|
33
|
+
this.pendingListeners.clear();
|
|
34
|
+
|
|
35
|
+
this.resolvedWriter = client.writer;
|
|
36
|
+
if (client.additionalReaders.length) {
|
|
37
|
+
return readWritePoolState(client.writer, client.additionalReaders);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return singleConnectionPoolState(client.writer);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async init() {
|
|
45
|
+
await this.state;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async close() {
|
|
49
|
+
const state = await this.state;
|
|
50
|
+
await state.close();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T> {
|
|
54
|
+
const state = await this.state;
|
|
55
|
+
return state.withConnection(true, fn, options);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async writeLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T> {
|
|
59
|
+
const state = await this.state;
|
|
60
|
+
return state.withConnection(false, fn, options);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async refreshSchema(): Promise<void> {
|
|
64
|
+
const state = await this.state;
|
|
65
|
+
await state.refreshSchema();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
registerListener(listener: Partial<DBAdapterListener>): () => void {
|
|
69
|
+
if (this.resolvedWriter) {
|
|
70
|
+
return this.resolvedWriter.registerListener(listener);
|
|
71
|
+
} else {
|
|
72
|
+
const pending: PendingListener = { listener };
|
|
73
|
+
this.pendingListeners.add(pending);
|
|
74
|
+
return () => {
|
|
75
|
+
if (pending.closeAfterRegisteredOnResolvedPool) {
|
|
76
|
+
return pending.closeAfterRegisteredOnResolvedPool();
|
|
77
|
+
} else {
|
|
78
|
+
// Has not been registered yet, we can just remove the pending listener.
|
|
79
|
+
this.pendingListeners.delete(pending);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface PoolConnection {
|
|
87
|
+
writer: DatabaseClient;
|
|
88
|
+
additionalReaders: DatabaseClient[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface PoolState {
|
|
92
|
+
writer: DatabaseClient;
|
|
93
|
+
withConnection<T>(allowReadOnly: boolean, fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T>;
|
|
94
|
+
close(): Promise<void>;
|
|
95
|
+
refreshSchema(): Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function singleConnectionPoolState(connection: DatabaseClient): PoolState {
|
|
99
|
+
return {
|
|
100
|
+
writer: connection,
|
|
101
|
+
withConnection: (allowReadOnly, fn, options) => {
|
|
102
|
+
if (allowReadOnly) {
|
|
103
|
+
return connection.readLock(fn, options);
|
|
104
|
+
} else {
|
|
105
|
+
return connection.writeLock(fn, options);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
close: () => connection.close(),
|
|
109
|
+
refreshSchema: () => connection.refreshSchema()
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readWritePoolState(writer: DatabaseClient, readers: DatabaseClient[]): PoolState {
|
|
114
|
+
// DatabaseClients have locks internally, so these aren't necessary for correctness. However, our mutex and semaphore
|
|
115
|
+
// implementations are very cheap to cancel, which we use to dispatch reads to the first available connection (by
|
|
116
|
+
// simply requesting all of them and sticking with the first connection we get).
|
|
117
|
+
const writerMutex = new Mutex();
|
|
118
|
+
const readerSemaphore = new Semaphore(readers);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
writer,
|
|
122
|
+
async withConnection(allowReadOnly, fn, options) {
|
|
123
|
+
const abortController = new AbortController();
|
|
124
|
+
const abortSignal = abortController.signal;
|
|
125
|
+
|
|
126
|
+
let timeout: any = null;
|
|
127
|
+
let release: UnlockFn | undefined;
|
|
128
|
+
if (options?.timeoutMs) {
|
|
129
|
+
timeout = setTimeout(() => abortController.abort, options.timeoutMs);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
if (allowReadOnly) {
|
|
134
|
+
let connection: DatabaseClient;
|
|
135
|
+
|
|
136
|
+
// Even if we have a pool of read connections, it's typically very small and we assume that most queries are
|
|
137
|
+
// reads. So, we want to request any connection from the read pool and the dedicated write connection (which
|
|
138
|
+
// can also serve reads). We race for the first connection we can obtain this way, and then abort the other
|
|
139
|
+
// request.
|
|
140
|
+
[connection, release] = await new Promise<[DatabaseClient, UnlockFn]>((resolve, reject) => {
|
|
141
|
+
let didComplete = false;
|
|
142
|
+
function complete() {
|
|
143
|
+
didComplete = true;
|
|
144
|
+
abortController.abort();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function completeSuccess(connection: DatabaseClient, returnFn: UnlockFn) {
|
|
148
|
+
if (didComplete) {
|
|
149
|
+
// We're not going to use this connection, so return it immediately.
|
|
150
|
+
returnFn();
|
|
151
|
+
} else {
|
|
152
|
+
complete();
|
|
153
|
+
resolve([connection, returnFn]);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function completeError(error: unknown) {
|
|
158
|
+
// We either have a working connection already, or we've rejected the promise. Either way, we don't need
|
|
159
|
+
// to do either thing again.
|
|
160
|
+
if (didComplete) return;
|
|
161
|
+
|
|
162
|
+
complete();
|
|
163
|
+
reject(error);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
writerMutex.acquire(abortSignal).then((unlock) => completeSuccess(writer, unlock), completeError);
|
|
167
|
+
readerSemaphore
|
|
168
|
+
.requestOne(abortSignal)
|
|
169
|
+
.then(({ item, release }) => completeSuccess(item, release), completeError);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return await connection.readLock(fn);
|
|
173
|
+
} else {
|
|
174
|
+
return await writerMutex.runExclusive(() => writer.writeLock(fn), abortSignal);
|
|
175
|
+
}
|
|
176
|
+
} finally {
|
|
177
|
+
if (timeout != null) {
|
|
178
|
+
clearTimeout(timeout);
|
|
179
|
+
}
|
|
180
|
+
release?.();
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
async close() {
|
|
184
|
+
await writer.close();
|
|
185
|
+
await Promise.all(readers.map((r) => r.close()));
|
|
186
|
+
},
|
|
187
|
+
async refreshSchema() {
|
|
188
|
+
await writer.refreshSchema();
|
|
189
|
+
await Promise.all(readers.map((r) => r.refreshSchema()));
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export class AsyncDbAdapter extends DBAdapterDefaultMixin(AsyncConnectionPool) implements WebDBAdapter {
|
|
195
|
+
async shareConnection(): Promise<SharedConnectionWorker> {
|
|
196
|
+
const state = await this.state;
|
|
197
|
+
return state.writer.shareConnection();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
getConfiguration(): WebDBAdapterConfiguration {
|
|
201
|
+
if (this.resolvedWriter) {
|
|
202
|
+
return this.resolvedWriter.getConfiguration();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
throw new Error('AsyncDbAdapter.getConfiguration() can only be called after initializing it.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
DBLockOptions,
|
|
6
6
|
LockContext,
|
|
7
7
|
QueryResult,
|
|
8
|
-
Transaction
|
|
8
|
+
Transaction,
|
|
9
|
+
Mutex,
|
|
10
|
+
timeoutSignal
|
|
9
11
|
} from '@powersync/common';
|
|
10
12
|
|
|
11
|
-
import { Mutex } from 'async-mutex';
|
|
12
|
-
|
|
13
13
|
const MOCK_QUERY_RESPONSE: QueryResult = {
|
|
14
14
|
rowsAffected: 0
|
|
15
15
|
};
|
|
@@ -34,19 +34,19 @@ export class SSRDBAdapter extends BaseObserver<DBAdapterListener> implements DBA
|
|
|
34
34
|
close() {}
|
|
35
35
|
|
|
36
36
|
async readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) {
|
|
37
|
-
return this.readMutex.runExclusive(() => fn(this));
|
|
37
|
+
return this.readMutex.runExclusive(() => fn(this), timeoutSignal(options?.timeoutMs));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) {
|
|
41
|
-
return this.readLock(() => fn(this.generateMockTransactionContext()));
|
|
41
|
+
return this.readLock(() => fn(this.generateMockTransactionContext()), options);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
async writeLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) {
|
|
45
|
-
return this.writeMutex.runExclusive(() => fn(this));
|
|
45
|
+
return this.writeMutex.runExclusive(() => fn(this), timeoutSignal(options?.timeoutMs));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async writeTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) {
|
|
49
|
-
return this.writeLock(() => fn(this.generateMockTransactionContext()));
|
|
49
|
+
return this.writeLock(() => fn(this.generateMockTransactionContext()), options);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
async execute(query: string, params?: any[]): Promise<QueryResult> {
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Mutex, UnlockFn } from '@powersync/common';
|
|
2
|
+
import { RawSqliteConnection } from './RawSqliteConnection.js';
|
|
3
|
+
import { ResolvedWASQLiteOpenFactoryOptions } from './WASQLiteOpenFactory.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A wrapper around a {@link RawSqliteConnection} allowing multiple tabs to access it.
|
|
7
|
+
*
|
|
8
|
+
* To allow potentially concurrent accesses from different clients, this requires a local mutex implementation here.
|
|
9
|
+
*
|
|
10
|
+
* Note that instances of this class are not safe to proxy across context boundaries with comlink! We need to be able to
|
|
11
|
+
* rely on mutexes being returned reliably, so additional checks to detect say a client tab closing are required to
|
|
12
|
+
* avoid deadlocks.
|
|
13
|
+
*/
|
|
14
|
+
export class ConcurrentSqliteConnection {
|
|
15
|
+
/**
|
|
16
|
+
* An outer mutex ensuring at most one {@link ConnectionLeaseToken} can exist for this connection at a time.
|
|
17
|
+
*
|
|
18
|
+
* If null, we'll use navigator locks instead.
|
|
19
|
+
*/
|
|
20
|
+
private leaseMutex: Mutex | null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param needsNavigatorLocks Whether access to the database needs an additional navigator lock guard.
|
|
24
|
+
*
|
|
25
|
+
* While {@link ConcurrentSqliteConnection} prevents concurrent access to a database _connection_, it's possible we
|
|
26
|
+
* might have multiple connections to the same physical database (e.g. if multiple tabs use dedicated workers).
|
|
27
|
+
* In those setups, we use navigator locks instead of an internal mutex to guard access..
|
|
28
|
+
*/
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly inner: RawSqliteConnection,
|
|
31
|
+
needsNavigatorLocks: boolean
|
|
32
|
+
) {
|
|
33
|
+
this.leaseMutex = needsNavigatorLocks ? null : new Mutex();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get options(): ResolvedWASQLiteOpenFactoryOptions {
|
|
37
|
+
return this.inner.options;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
acquireMutex(abort?: AbortSignal): Promise<UnlockFn> {
|
|
41
|
+
if (this.leaseMutex) {
|
|
42
|
+
return this.leaseMutex.acquire(abort);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const options: LockOptions = { signal: abort };
|
|
47
|
+
|
|
48
|
+
navigator.locks
|
|
49
|
+
.request(`db-lock-${this.options.dbFilename}`, options, (_) => {
|
|
50
|
+
return new Promise<void>((returnLock) => {
|
|
51
|
+
return resolve(() => {
|
|
52
|
+
returnLock();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
})
|
|
56
|
+
.catch(reject);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Unsafe, unguarded access to the SQLite connection.
|
|
61
|
+
unsafeUseInner(): RawSqliteConnection {
|
|
62
|
+
return this.inner;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @returns A {@link ConnectionLeaseToken}. Until that token is returned, no other client can use the database.
|
|
67
|
+
*/
|
|
68
|
+
async acquireConnection(abort?: AbortSignal): Promise<ConnectionLeaseToken> {
|
|
69
|
+
const returnMutex = await this.acquireMutex(abort);
|
|
70
|
+
const token = new ConnectionLeaseToken(returnMutex, this.inner);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// Assert that the inner connection is initialized at this point, fail early if it's not.
|
|
74
|
+
this.inner.requireSqlite();
|
|
75
|
+
|
|
76
|
+
// If a previous client was interrupted in the middle of a transaction AND this is a shared worker, it's possible
|
|
77
|
+
// for the connection to still be in a transaction. To avoid inconsistent state, we roll back connection leases
|
|
78
|
+
// that haven't been comitted.
|
|
79
|
+
if (!this.inner.isAutoCommit()) {
|
|
80
|
+
await this.inner.executeRaw('ROLLBACK');
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
returnMutex();
|
|
84
|
+
throw e;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return token;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async close(): Promise<void> {
|
|
91
|
+
const returnMutex = await this.acquireMutex();
|
|
92
|
+
try {
|
|
93
|
+
await this.inner.close();
|
|
94
|
+
} finally {
|
|
95
|
+
returnMutex();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* An instance representing temporary exclusive access to a {@link ConcurrentSqliteConnection}.
|
|
102
|
+
*/
|
|
103
|
+
export class ConnectionLeaseToken {
|
|
104
|
+
/** Ensures that the client with access to this token can't run statements concurrently. */
|
|
105
|
+
private useMutex: Mutex = new Mutex();
|
|
106
|
+
private closed = false;
|
|
107
|
+
|
|
108
|
+
constructor(
|
|
109
|
+
private returnMutex: UnlockFn,
|
|
110
|
+
private connection: RawSqliteConnection
|
|
111
|
+
) {}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns this lease, allowing another client to use the database connection.
|
|
115
|
+
*/
|
|
116
|
+
async returnLease() {
|
|
117
|
+
await this.useMutex.runExclusive(async () => {
|
|
118
|
+
if (!this.closed) {
|
|
119
|
+
this.closed = true;
|
|
120
|
+
this.returnMutex();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* This should only be used internally, since the callback must not use the raw connection after resolving.
|
|
127
|
+
*/
|
|
128
|
+
async use<T>(callback: (conn: RawSqliteConnection) => Promise<T>): Promise<T> {
|
|
129
|
+
return await this.useMutex.runExclusive(async () => {
|
|
130
|
+
if (this.closed) {
|
|
131
|
+
throw new Error('lease token has already been closed');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return await callback(this.connection);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|