@powersync/web 0.0.0-dev-20260414110516 → 0.0.0-dev-20260504100448
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 +4289 -4786
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +37 -798
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +99 -737
- 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 -7
- package/lib/src/db/PowerSyncDatabase.d.ts +1 -1
- package/lib/src/db/PowerSyncDatabase.js +0 -8
- package/lib/src/db/adapters/AsyncWebAdapter.d.ts +13 -3
- package/lib/src/db/adapters/AsyncWebAdapter.js +115 -21
- package/lib/src/db/adapters/wa-sqlite/DatabaseServer.js +1 -2
- package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js +1 -1
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +16 -2
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +60 -38
- package/lib/src/db/adapters/wa-sqlite/vfs.d.ts +7 -18
- package/lib/src/db/adapters/wa-sqlite/vfs.js +34 -49
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +4 -0
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +4 -0
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +0 -1
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +0 -3
- package/lib/src/db/sync/WebRemote.d.ts +1 -3
- package/lib/src/db/sync/WebRemote.js +0 -12
- package/lib/src/worker/db/MultiDatabaseServer.js +4 -1
- package/lib/src/worker/db/open-worker-database.js +2 -2
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +0 -2
- package/lib/src/worker/sync/SharedSyncImplementation.js +4 -16
- package/lib/src/worker/sync/WorkerClient.d.ts +0 -1
- package/lib/src/worker/sync/WorkerClient.js +0 -3
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -8
- package/src/db/PowerSyncDatabase.ts +1 -9
- package/src/db/adapters/AsyncWebAdapter.ts +138 -22
- package/src/db/adapters/wa-sqlite/DatabaseServer.ts +4 -2
- package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +4 -1
- package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +89 -44
- package/src/db/adapters/wa-sqlite/vfs.ts +33 -49
- package/src/db/sync/SSRWebStreamingSyncImplementation.ts +5 -0
- package/src/db/sync/SharedWebStreamingSyncImplementation.ts +0 -4
- package/src/db/sync/WebRemote.ts +0 -16
- package/src/worker/db/MultiDatabaseServer.ts +4 -1
- package/src/worker/db/open-worker-database.ts +2 -2
- package/src/worker/sync/SharedSyncImplementation.ts +4 -18
- package/src/worker/sync/WorkerClient.ts +0 -4
- package/dist/26d61ca9f5694d064635.wasm +0 -0
- package/dist/b4c6283dc473b6b3fd24.wasm +0 -0
- package/dist/c78985091a0b22aaef03.wasm +0 -0
- package/dist/ca59e199e1138b553fad.wasm +0 -0
- package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js +0 -4646
- package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.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-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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/web",
|
|
3
|
-
"version": "1.37.
|
|
3
|
+
"version": "1.37.2",
|
|
4
4
|
"description": "PowerSync Web SDK",
|
|
5
5
|
"main": "lib/src/index.js",
|
|
6
6
|
"module": "lib/src/index.js",
|
|
@@ -67,26 +67,23 @@
|
|
|
67
67
|
"license": "Apache-2.0",
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"@journeyapps/wa-sqlite": "catalog:",
|
|
70
|
-
"@powersync/common": "workspace:^1.
|
|
70
|
+
"@powersync/common": "workspace:^1.52.0"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@powersync/common": "workspace:*",
|
|
74
|
-
"bson": "catalog:",
|
|
75
74
|
"comlink": "catalog:",
|
|
76
75
|
"commander": "^12.1.0"
|
|
77
76
|
},
|
|
78
77
|
"devDependencies": {
|
|
79
|
-
"@journeyapps/wa-sqlite": "^1.
|
|
78
|
+
"@journeyapps/wa-sqlite": "^1.7.0",
|
|
80
79
|
"@types/uuid": "catalog:",
|
|
81
|
-
"
|
|
80
|
+
"bson": "catalog:",
|
|
82
81
|
"glob": "catalog:",
|
|
83
82
|
"p-defer": "catalog:",
|
|
84
83
|
"source-map-loader": "^5.0.0",
|
|
85
|
-
"stream-browserify": "^3.0.0",
|
|
86
84
|
"terser-webpack-plugin": "^5.3.9",
|
|
87
85
|
"uuid": "catalog:",
|
|
88
86
|
"vite": "catalog:",
|
|
89
|
-
"vm-browserify": "^1.1.2",
|
|
90
87
|
"webpack": "^5.90.1",
|
|
91
88
|
"webpack-cli": "^5.1.4",
|
|
92
89
|
"webpack-node-externals": "^3.0.0"
|
|
@@ -2,6 +2,7 @@ import { AbstractPowerSyncDatabase, DBAdapter, PowerSyncDatabaseOptions, PowerSy
|
|
|
2
2
|
import { ResolvedWebSQLOpenOptions, WebSQLFlags } from './adapters/web-sql-flags.js';
|
|
3
3
|
export interface WebPowerSyncFlags extends WebSQLFlags {
|
|
4
4
|
/**
|
|
5
|
+
* @deprecated This flag is no longer used. Navigator locks now handle tab detection automatically.
|
|
5
6
|
* Externally unload open PowerSync database instances when the window closes.
|
|
6
7
|
* Setting this to `true` requires calling `close` on all open PowerSyncDatabase
|
|
7
8
|
* instances before the window unloads
|
|
@@ -54,7 +55,6 @@ export declare const resolveWebPowerSyncFlags: (flags?: WebPowerSyncFlags) => Re
|
|
|
54
55
|
export declare class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
55
56
|
protected options: WebPowerSyncDatabaseOptions;
|
|
56
57
|
static SHARED_MUTEX: Mutex;
|
|
57
|
-
protected unloadListener?: () => Promise<void>;
|
|
58
58
|
protected resolvedFlags: WebPowerSyncFlags;
|
|
59
59
|
constructor(options: WebPowerSyncDatabaseOptionsWithAdapter);
|
|
60
60
|
constructor(options: WebPowerSyncDatabaseOptionsWithOpenFactory);
|
|
@@ -47,17 +47,12 @@ function assertValidDatabaseOptions(options) {
|
|
|
47
47
|
export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
48
48
|
options;
|
|
49
49
|
static SHARED_MUTEX = new Mutex();
|
|
50
|
-
unloadListener;
|
|
51
50
|
resolvedFlags;
|
|
52
51
|
constructor(options) {
|
|
53
52
|
super(options);
|
|
54
53
|
this.options = options;
|
|
55
54
|
assertValidDatabaseOptions(options);
|
|
56
55
|
this.resolvedFlags = resolveWebPowerSyncFlags(options.flags);
|
|
57
|
-
if (this.resolvedFlags.enableMultiTabs && !this.resolvedFlags.externallyUnload) {
|
|
58
|
-
this.unloadListener = () => this.close({ disconnect: false });
|
|
59
|
-
window.addEventListener('unload', this.unloadListener);
|
|
60
|
-
}
|
|
61
56
|
}
|
|
62
57
|
async _initialize() {
|
|
63
58
|
if (this.database instanceof AsyncDbAdapter) {
|
|
@@ -98,9 +93,6 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
|
98
93
|
* multiple tabs are not enabled.
|
|
99
94
|
*/
|
|
100
95
|
close(options) {
|
|
101
|
-
if (this.unloadListener) {
|
|
102
|
-
window.removeEventListener('unload', this.unloadListener);
|
|
103
|
-
}
|
|
104
96
|
return super.close({
|
|
105
97
|
// Don't disconnect by default if multiple tabs are enabled
|
|
106
98
|
disconnect: options?.disconnect ?? !this.resolvedFlags.enableMultiTabs
|
|
@@ -6,10 +6,10 @@ import { DatabaseClient } from './wa-sqlite/DatabaseClient.js';
|
|
|
6
6
|
*/
|
|
7
7
|
declare class AsyncConnectionPool implements ConnectionPool {
|
|
8
8
|
readonly name: string;
|
|
9
|
-
protected readonly
|
|
10
|
-
protected
|
|
9
|
+
protected readonly state: Promise<PoolState>;
|
|
10
|
+
protected resolvedWriter?: DatabaseClient;
|
|
11
11
|
private readonly pendingListeners;
|
|
12
|
-
constructor(inner: Promise<
|
|
12
|
+
constructor(inner: Promise<PoolConnection>, name: string);
|
|
13
13
|
init(): Promise<void>;
|
|
14
14
|
close(): Promise<void>;
|
|
15
15
|
readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T>;
|
|
@@ -17,6 +17,16 @@ declare class AsyncConnectionPool implements ConnectionPool {
|
|
|
17
17
|
refreshSchema(): Promise<void>;
|
|
18
18
|
registerListener(listener: Partial<DBAdapterListener>): () => void;
|
|
19
19
|
}
|
|
20
|
+
export interface PoolConnection {
|
|
21
|
+
writer: DatabaseClient;
|
|
22
|
+
additionalReaders: DatabaseClient[];
|
|
23
|
+
}
|
|
24
|
+
interface PoolState {
|
|
25
|
+
writer: DatabaseClient;
|
|
26
|
+
withConnection<T>(allowReadOnly: boolean, fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T>;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
refreshSchema(): Promise<void>;
|
|
29
|
+
}
|
|
20
30
|
declare const AsyncDbAdapter_base: (new (...args: any[]) => {
|
|
21
31
|
readTransaction<T>(fn: (tx: import("@powersync/common").Transaction) => Promise<T>, options?: DBLockOptions): Promise<T>;
|
|
22
32
|
writeTransaction<T>(fn: (tx: import("@powersync/common").Transaction) => Promise<T>, options?: DBLockOptions): Promise<T>;
|
|
@@ -1,44 +1,48 @@
|
|
|
1
|
-
import { DBAdapterDefaultMixin } from '@powersync/common';
|
|
1
|
+
import { DBAdapterDefaultMixin, Mutex, Semaphore } from '@powersync/common';
|
|
2
2
|
/**
|
|
3
3
|
* A connection pool implementation delegating to another pool opened asynchronnously.
|
|
4
4
|
*/
|
|
5
5
|
class AsyncConnectionPool {
|
|
6
6
|
name;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
state;
|
|
8
|
+
resolvedWriter;
|
|
9
9
|
pendingListeners = new Set();
|
|
10
10
|
constructor(inner, name) {
|
|
11
11
|
this.name = name;
|
|
12
|
-
this.
|
|
12
|
+
this.state = inner.then((client) => {
|
|
13
13
|
for (const pending of this.pendingListeners) {
|
|
14
|
-
pending.closeAfterRegisteredOnResolvedPool = client.registerListener(pending.listener);
|
|
14
|
+
pending.closeAfterRegisteredOnResolvedPool = client.writer.registerListener(pending.listener);
|
|
15
15
|
}
|
|
16
16
|
this.pendingListeners.clear();
|
|
17
|
-
this.
|
|
18
|
-
|
|
17
|
+
this.resolvedWriter = client.writer;
|
|
18
|
+
if (client.additionalReaders.length) {
|
|
19
|
+
return readWritePoolState(client.writer, client.additionalReaders);
|
|
20
|
+
}
|
|
21
|
+
return singleConnectionPoolState(client.writer);
|
|
19
22
|
});
|
|
20
23
|
}
|
|
21
24
|
async init() {
|
|
22
|
-
await this.
|
|
25
|
+
await this.state;
|
|
23
26
|
}
|
|
24
27
|
async close() {
|
|
25
|
-
const
|
|
26
|
-
|
|
28
|
+
const state = await this.state;
|
|
29
|
+
await state.close();
|
|
27
30
|
}
|
|
28
31
|
async readLock(fn, options) {
|
|
29
|
-
const
|
|
30
|
-
return
|
|
32
|
+
const state = await this.state;
|
|
33
|
+
return state.withConnection(true, fn, options);
|
|
31
34
|
}
|
|
32
35
|
async writeLock(fn, options) {
|
|
33
|
-
const
|
|
34
|
-
return
|
|
36
|
+
const state = await this.state;
|
|
37
|
+
return state.withConnection(false, fn, options);
|
|
35
38
|
}
|
|
36
39
|
async refreshSchema() {
|
|
37
|
-
|
|
40
|
+
const state = await this.state;
|
|
41
|
+
await state.refreshSchema();
|
|
38
42
|
}
|
|
39
43
|
registerListener(listener) {
|
|
40
|
-
if (this.
|
|
41
|
-
return this.
|
|
44
|
+
if (this.resolvedWriter) {
|
|
45
|
+
return this.resolvedWriter.registerListener(listener);
|
|
42
46
|
}
|
|
43
47
|
else {
|
|
44
48
|
const pending = { listener };
|
|
@@ -55,14 +59,104 @@ class AsyncConnectionPool {
|
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
}
|
|
62
|
+
function singleConnectionPoolState(connection) {
|
|
63
|
+
return {
|
|
64
|
+
writer: connection,
|
|
65
|
+
withConnection: (allowReadOnly, fn, options) => {
|
|
66
|
+
if (allowReadOnly) {
|
|
67
|
+
return connection.readLock(fn, options);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
return connection.writeLock(fn, options);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
close: () => connection.close(),
|
|
74
|
+
refreshSchema: () => connection.refreshSchema()
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function readWritePoolState(writer, readers) {
|
|
78
|
+
// DatabaseClients have locks internally, so these aren't necessary for correctness. However, our mutex and semaphore
|
|
79
|
+
// implementations are very cheap to cancel, which we use to dispatch reads to the first available connection (by
|
|
80
|
+
// simply requesting all of them and sticking with the first connection we get).
|
|
81
|
+
const writerMutex = new Mutex();
|
|
82
|
+
const readerSemaphore = new Semaphore(readers);
|
|
83
|
+
return {
|
|
84
|
+
writer,
|
|
85
|
+
async withConnection(allowReadOnly, fn, options) {
|
|
86
|
+
const abortController = new AbortController();
|
|
87
|
+
const abortSignal = abortController.signal;
|
|
88
|
+
let timeout = null;
|
|
89
|
+
let release;
|
|
90
|
+
if (options?.timeoutMs) {
|
|
91
|
+
timeout = setTimeout(() => abortController.abort, options.timeoutMs);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
if (allowReadOnly) {
|
|
95
|
+
let connection;
|
|
96
|
+
// Even if we have a pool of read connections, it's typically very small and we assume that most queries are
|
|
97
|
+
// reads. So, we want to request any connection from the read pool and the dedicated write connection (which
|
|
98
|
+
// can also serve reads). We race for the first connection we can obtain this way, and then abort the other
|
|
99
|
+
// request.
|
|
100
|
+
[connection, release] = await new Promise((resolve, reject) => {
|
|
101
|
+
let didComplete = false;
|
|
102
|
+
function complete() {
|
|
103
|
+
didComplete = true;
|
|
104
|
+
abortController.abort();
|
|
105
|
+
}
|
|
106
|
+
function completeSuccess(connection, returnFn) {
|
|
107
|
+
if (didComplete) {
|
|
108
|
+
// We're not going to use this connection, so return it immediately.
|
|
109
|
+
returnFn();
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
complete();
|
|
113
|
+
resolve([connection, returnFn]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function completeError(error) {
|
|
117
|
+
// We either have a working connection already, or we've rejected the promise. Either way, we don't need
|
|
118
|
+
// to do either thing again.
|
|
119
|
+
if (didComplete)
|
|
120
|
+
return;
|
|
121
|
+
complete();
|
|
122
|
+
reject(error);
|
|
123
|
+
}
|
|
124
|
+
writerMutex.acquire(abortSignal).then((unlock) => completeSuccess(writer, unlock), completeError);
|
|
125
|
+
readerSemaphore
|
|
126
|
+
.requestOne(abortSignal)
|
|
127
|
+
.then(({ item, release }) => completeSuccess(item, release), completeError);
|
|
128
|
+
});
|
|
129
|
+
return await connection.readLock(fn);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
return await writerMutex.runExclusive(() => writer.writeLock(fn), abortSignal);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
if (timeout != null) {
|
|
137
|
+
clearTimeout(timeout);
|
|
138
|
+
}
|
|
139
|
+
release?.();
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
async close() {
|
|
143
|
+
await writer.close();
|
|
144
|
+
await Promise.all(readers.map((r) => r.close()));
|
|
145
|
+
},
|
|
146
|
+
async refreshSchema() {
|
|
147
|
+
await writer.refreshSchema();
|
|
148
|
+
await Promise.all(readers.map((r) => r.refreshSchema()));
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
58
152
|
export class AsyncDbAdapter extends DBAdapterDefaultMixin(AsyncConnectionPool) {
|
|
59
153
|
async shareConnection() {
|
|
60
|
-
const
|
|
61
|
-
return
|
|
154
|
+
const state = await this.state;
|
|
155
|
+
return state.writer.shareConnection();
|
|
62
156
|
}
|
|
63
157
|
getConfiguration() {
|
|
64
|
-
if (this.
|
|
65
|
-
return this.
|
|
158
|
+
if (this.resolvedWriter) {
|
|
159
|
+
return this.resolvedWriter.getConfiguration();
|
|
66
160
|
}
|
|
67
161
|
throw new Error('AsyncDbAdapter.getConfiguration() can only be called after initializing it.');
|
|
68
162
|
}
|
|
@@ -85,8 +85,7 @@ export class DatabaseServer {
|
|
|
85
85
|
},
|
|
86
86
|
requestAccess: async (write, timeoutMs) => {
|
|
87
87
|
requireOpen();
|
|
88
|
-
|
|
89
|
-
const lease = await this.#inner.acquireConnection();
|
|
88
|
+
const lease = await this.#inner.acquireConnection(timeoutMs != null ? AbortSignal.timeout(timeoutMs) : undefined);
|
|
90
89
|
if (!isOpen) {
|
|
91
90
|
// Race between requestAccess and close(), the connection was closed while we tried to acquire a lease.
|
|
92
91
|
await lease.returnLease();
|
|
@@ -23,7 +23,7 @@ export class RawSqliteConnection {
|
|
|
23
23
|
}
|
|
24
24
|
async init() {
|
|
25
25
|
const api = (this._sqliteAPI = await this.openSQLiteAPI());
|
|
26
|
-
this.db = await api.open_v2(this.options.dbFilename);
|
|
26
|
+
this.db = await api.open_v2(this.options.dbFilename, this.options.isReadOnly ? 1 /* SQLITE_OPEN_READONLY */ : 6 /* SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE */);
|
|
27
27
|
await this.executeRaw(`PRAGMA temp_store = ${this.options.temporaryStorage};`);
|
|
28
28
|
if (this.options.encryptionKey) {
|
|
29
29
|
const escapedKey = this.options.encryptionKey.replace("'", "''");
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { DBAdapter, SQLOpenFactory, type ILogLevel } from '@powersync/common';
|
|
2
2
|
import { ResolvedWebSQLOpenOptions, WebSQLOpenFactoryOptions } from '../web-sql-flags.js';
|
|
3
3
|
import { WASQLiteVFS } from './vfs.js';
|
|
4
|
-
import {
|
|
4
|
+
import { PoolConnection } from '../AsyncWebAdapter.js';
|
|
5
5
|
export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions {
|
|
6
6
|
vfs?: WASQLiteVFS;
|
|
7
|
+
/**
|
|
8
|
+
* If the {@link vfs} supports it, an additional amount of read-only connections to open. Using additional read
|
|
9
|
+
* connections can speed up queries by dispatching them to multiple workers running them concurrently.
|
|
10
|
+
*
|
|
11
|
+
* {@link WASQLiteVFS.OPFSWriteAheadVFS} is the only VFS with support for multiple connections, so this option is
|
|
12
|
+
* ignored for other VFS implementations.
|
|
13
|
+
*
|
|
14
|
+
* Defaults to 1.
|
|
15
|
+
*/
|
|
16
|
+
additionalReaders?: number;
|
|
7
17
|
}
|
|
8
18
|
export interface ResolvedWASQLiteOpenFactoryOptions extends ResolvedWebSQLOpenOptions {
|
|
9
19
|
vfs: WASQLiteVFS;
|
|
20
|
+
/**
|
|
21
|
+
* Whether this is a read-only connection opened for the `OPFSWriteAheadVFS` file system.
|
|
22
|
+
*/
|
|
23
|
+
isReadOnly: boolean;
|
|
10
24
|
}
|
|
11
25
|
export interface WorkerDBOpenerOptions extends ResolvedWASQLiteOpenFactoryOptions {
|
|
12
26
|
logLevel: ILogLevel;
|
|
@@ -27,5 +41,5 @@ export declare class WASQLiteOpenFactory implements SQLOpenFactory {
|
|
|
27
41
|
get waOptions(): WASQLiteOpenFactoryOptions;
|
|
28
42
|
protected openAdapter(): DBAdapter;
|
|
29
43
|
openDB(): DBAdapter;
|
|
30
|
-
openConnection(): Promise<
|
|
44
|
+
openConnection(): Promise<PoolConnection>;
|
|
31
45
|
}
|
|
@@ -50,7 +50,7 @@ export class WASQLiteOpenFactory {
|
|
|
50
50
|
if (!enableMultiTabs) {
|
|
51
51
|
this.logger.warn('Multiple tabs are not enabled in this browser');
|
|
52
52
|
}
|
|
53
|
-
const
|
|
53
|
+
const resolveOptions = (isReadOnly) => ({
|
|
54
54
|
dbFilename: this.options.dbFilename,
|
|
55
55
|
dbLocation: this.options.dbLocation,
|
|
56
56
|
debugMode: this.options.debugMode,
|
|
@@ -58,55 +58,77 @@ export class WASQLiteOpenFactory {
|
|
|
58
58
|
temporaryStorage,
|
|
59
59
|
cacheSizeKb,
|
|
60
60
|
flags: this.resolvedFlags,
|
|
61
|
-
encryptionKey: encryptionKey
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
encryptionKey: encryptionKey,
|
|
62
|
+
isReadOnly
|
|
63
|
+
});
|
|
64
|
+
let client;
|
|
65
|
+
let additionalReaders = [];
|
|
64
66
|
let requiresPersistentTriggers = vfsRequiresDedicatedWorkers(vfs);
|
|
65
67
|
if (useWebWorker) {
|
|
66
68
|
const optionsDbWorker = this.options.worker;
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
workerPort
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
69
|
+
const openDatabaseWorker = async (resolvedOptions) => {
|
|
70
|
+
const workerPort = typeof optionsDbWorker == 'function'
|
|
71
|
+
? resolveWorkerDatabasePortFactory(() => optionsDbWorker({
|
|
72
|
+
...this.options,
|
|
73
|
+
temporaryStorage,
|
|
74
|
+
cacheSizeKb,
|
|
75
|
+
flags: this.resolvedFlags,
|
|
76
|
+
encryptionKey
|
|
77
|
+
}))
|
|
78
|
+
: openWorkerDatabasePort(this.options.dbFilename, enableMultiTabs, optionsDbWorker, this.waOptions.vfs);
|
|
79
|
+
const source = Comlink.wrap(workerPort);
|
|
80
|
+
const closeSignal = new AbortController();
|
|
81
|
+
const connection = await source.connect({
|
|
82
|
+
...resolvedOptions,
|
|
83
|
+
logLevel: this.logger.getLevel(),
|
|
84
|
+
lockName: await generateTabCloseSignal(closeSignal.signal)
|
|
85
|
+
});
|
|
86
|
+
const clientOptions = {
|
|
87
|
+
connection,
|
|
88
|
+
source,
|
|
89
|
+
// This tab owns the worker, so we're guaranteed to outlive it.
|
|
90
|
+
remoteCanCloseUnexpectedly: false,
|
|
91
|
+
onClose: () => {
|
|
92
|
+
closeSignal.abort();
|
|
93
|
+
if (workerPort instanceof Worker) {
|
|
94
|
+
workerPort.terminate();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
workerPort.close();
|
|
98
|
+
}
|
|
95
99
|
}
|
|
96
|
-
}
|
|
100
|
+
};
|
|
101
|
+
return new DatabaseClient(clientOptions, {
|
|
102
|
+
...resolvedOptions,
|
|
103
|
+
requiresPersistentTriggers
|
|
104
|
+
});
|
|
97
105
|
};
|
|
106
|
+
client = await openDatabaseWorker(resolveOptions(false));
|
|
107
|
+
if (vfs == WASQLiteVFS.OPFSWriteAheadVFS) {
|
|
108
|
+
// This VFS supports concurrent reads, so we can open additional workers to host read-only connections for
|
|
109
|
+
// concurrent reads / writes.
|
|
110
|
+
const additionalReadersCount = this.options.additionalReaders ?? 1;
|
|
111
|
+
for (let i = 0; i < additionalReadersCount; i++) {
|
|
112
|
+
const reader = await openDatabaseWorker(resolveOptions(true));
|
|
113
|
+
additionalReaders.push(reader);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
98
116
|
}
|
|
99
117
|
else {
|
|
100
118
|
// Don't use a web worker. Instead, open the MultiDatabaseServer a worker would use locally.
|
|
101
119
|
const localServer = new MultiDatabaseServer(this.logger);
|
|
102
120
|
requiresPersistentTriggers = true;
|
|
121
|
+
const resolvedOptions = resolveOptions(false);
|
|
103
122
|
const connection = await localServer.openConnectionLocally(resolvedOptions);
|
|
104
|
-
|
|
123
|
+
client = new DatabaseClient({ connection, source: null, remoteCanCloseUnexpectedly: false }, {
|
|
124
|
+
...resolvedOptions,
|
|
125
|
+
requiresPersistentTriggers
|
|
126
|
+
});
|
|
105
127
|
}
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
128
|
+
return {
|
|
129
|
+
writer: client,
|
|
130
|
+
additionalReaders
|
|
131
|
+
};
|
|
110
132
|
}
|
|
111
133
|
}
|
|
112
134
|
/**
|
|
@@ -5,9 +5,10 @@ import type * as SQLite from '@journeyapps/wa-sqlite';
|
|
|
5
5
|
export declare enum WASQLiteVFS {
|
|
6
6
|
IDBBatchAtomicVFS = "IDBBatchAtomicVFS",
|
|
7
7
|
OPFSCoopSyncVFS = "OPFSCoopSyncVFS",
|
|
8
|
-
AccessHandlePoolVFS = "AccessHandlePoolVFS"
|
|
8
|
+
AccessHandlePoolVFS = "AccessHandlePoolVFS",
|
|
9
|
+
OPFSWriteAheadVFS = "OPFSWriteAheadVFS"
|
|
9
10
|
}
|
|
10
|
-
export declare function vfsRequiresDedicatedWorkers(vfs: WASQLiteVFS): vfs is WASQLiteVFS.OPFSCoopSyncVFS | WASQLiteVFS.AccessHandlePoolVFS;
|
|
11
|
+
export declare function vfsRequiresDedicatedWorkers(vfs: WASQLiteVFS): vfs is WASQLiteVFS.OPFSCoopSyncVFS | WASQLiteVFS.AccessHandlePoolVFS | WASQLiteVFS.OPFSWriteAheadVFS;
|
|
11
12
|
/**
|
|
12
13
|
* @internal
|
|
13
14
|
*/
|
|
@@ -26,22 +27,6 @@ export type WASQLiteModuleFactory = (options: WASQLiteModuleFactoryOptions) => P
|
|
|
26
27
|
module: SQLiteModule;
|
|
27
28
|
vfs: SQLiteVFS;
|
|
28
29
|
}>;
|
|
29
|
-
/**
|
|
30
|
-
* @internal
|
|
31
|
-
*/
|
|
32
|
-
export declare const AsyncWASQLiteModuleFactory: () => Promise<any>;
|
|
33
|
-
/**
|
|
34
|
-
* @internal
|
|
35
|
-
*/
|
|
36
|
-
export declare const MultiCipherAsyncWASQLiteModuleFactory: () => Promise<any>;
|
|
37
|
-
/**
|
|
38
|
-
* @internal
|
|
39
|
-
*/
|
|
40
|
-
export declare const SyncWASQLiteModuleFactory: () => Promise<any>;
|
|
41
|
-
/**
|
|
42
|
-
* @internal
|
|
43
|
-
*/
|
|
44
|
-
export declare const MultiCipherSyncWASQLiteModuleFactory: () => Promise<any>;
|
|
45
30
|
/**
|
|
46
31
|
* @internal
|
|
47
32
|
*/
|
|
@@ -58,4 +43,8 @@ export declare const DEFAULT_MODULE_FACTORIES: {
|
|
|
58
43
|
module: any;
|
|
59
44
|
vfs: any;
|
|
60
45
|
}>;
|
|
46
|
+
OPFSWriteAheadVFS: (options: WASQLiteModuleFactoryOptions) => Promise<{
|
|
47
|
+
module: any;
|
|
48
|
+
vfs: any;
|
|
49
|
+
}>;
|
|
61
50
|
};
|
|
@@ -6,50 +6,37 @@ export var WASQLiteVFS;
|
|
|
6
6
|
WASQLiteVFS["IDBBatchAtomicVFS"] = "IDBBatchAtomicVFS";
|
|
7
7
|
WASQLiteVFS["OPFSCoopSyncVFS"] = "OPFSCoopSyncVFS";
|
|
8
8
|
WASQLiteVFS["AccessHandlePoolVFS"] = "AccessHandlePoolVFS";
|
|
9
|
+
WASQLiteVFS["OPFSWriteAheadVFS"] = "OPFSWriteAheadVFS";
|
|
9
10
|
})(WASQLiteVFS || (WASQLiteVFS = {}));
|
|
10
11
|
export function vfsRequiresDedicatedWorkers(vfs) {
|
|
11
12
|
return vfs != WASQLiteVFS.IDBBatchAtomicVFS;
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
};
|
|
14
|
+
async function asyncModuleFactory(encryptionKey) {
|
|
15
|
+
if (encryptionKey) {
|
|
16
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs');
|
|
17
|
+
return factory();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite-async.mjs');
|
|
21
|
+
return factory();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function syncModuleFactory(encryptionKey) {
|
|
25
|
+
if (encryptionKey) {
|
|
26
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite.mjs');
|
|
27
|
+
return factory();
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite.mjs');
|
|
31
|
+
return factory();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
41
34
|
/**
|
|
42
35
|
* @internal
|
|
43
36
|
*/
|
|
44
37
|
export const DEFAULT_MODULE_FACTORIES = {
|
|
45
38
|
[WASQLiteVFS.IDBBatchAtomicVFS]: async (options) => {
|
|
46
|
-
|
|
47
|
-
if (options.encryptionKey) {
|
|
48
|
-
module = await MultiCipherAsyncWASQLiteModuleFactory();
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
module = await AsyncWASQLiteModuleFactory();
|
|
52
|
-
}
|
|
39
|
+
const module = await asyncModuleFactory(options.encryptionKey);
|
|
53
40
|
const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js');
|
|
54
41
|
return {
|
|
55
42
|
module,
|
|
@@ -58,13 +45,7 @@ export const DEFAULT_MODULE_FACTORIES = {
|
|
|
58
45
|
};
|
|
59
46
|
},
|
|
60
47
|
[WASQLiteVFS.AccessHandlePoolVFS]: async (options) => {
|
|
61
|
-
|
|
62
|
-
if (options.encryptionKey) {
|
|
63
|
-
module = await MultiCipherSyncWASQLiteModuleFactory();
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
module = await SyncWASQLiteModuleFactory();
|
|
67
|
-
}
|
|
48
|
+
const module = await syncModuleFactory(options.encryptionKey);
|
|
68
49
|
// @ts-expect-error The types for this static method are missing upstream
|
|
69
50
|
const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js');
|
|
70
51
|
return {
|
|
@@ -73,13 +54,7 @@ export const DEFAULT_MODULE_FACTORIES = {
|
|
|
73
54
|
};
|
|
74
55
|
},
|
|
75
56
|
[WASQLiteVFS.OPFSCoopSyncVFS]: async (options) => {
|
|
76
|
-
|
|
77
|
-
if (options.encryptionKey) {
|
|
78
|
-
module = await MultiCipherSyncWASQLiteModuleFactory();
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
module = await SyncWASQLiteModuleFactory();
|
|
82
|
-
}
|
|
57
|
+
const module = await syncModuleFactory(options.encryptionKey);
|
|
83
58
|
// @ts-expect-error The types for this static method are missing upstream
|
|
84
59
|
const { OPFSCoopSyncVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js');
|
|
85
60
|
const vfs = await OPFSCoopSyncVFS.create(options.dbFileName, module);
|
|
@@ -87,5 +62,15 @@ export const DEFAULT_MODULE_FACTORIES = {
|
|
|
87
62
|
module,
|
|
88
63
|
vfs
|
|
89
64
|
};
|
|
65
|
+
},
|
|
66
|
+
[WASQLiteVFS.OPFSWriteAheadVFS]: async (options) => {
|
|
67
|
+
const module = await syncModuleFactory(options.encryptionKey);
|
|
68
|
+
// @ts-expect-error The types for this static method are missing upstream
|
|
69
|
+
const { OPFSWriteAheadVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSWriteAheadVFS.js');
|
|
70
|
+
const vfs = await OPFSWriteAheadVFS.create(options.dbFileName, module, {});
|
|
71
|
+
return {
|
|
72
|
+
module,
|
|
73
|
+
vfs
|
|
74
|
+
};
|
|
90
75
|
}
|
|
91
76
|
};
|