@powersync/web 0.0.0-dev-20250925184532 → 0.0.0-dev-20251003085035
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/{fbde47713220d7baec73.wasm → 10072fe45f0a8fab0a0e.wasm} +0 -0
- package/dist/{d0a1e43030b814ed322f.wasm → 6e435e51534839845554.wasm} +0 -0
- package/dist/a730f7ca717b02234beb.wasm +0 -0
- package/dist/aa2f408d64445fed090e.wasm +0 -0
- package/dist/index.umd.js +82 -26
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +76 -21
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +2 -2
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -1
- package/lib/package.json +4 -4
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +11 -0
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +53 -9
- package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +1 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +2 -0
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +2 -0
- package/lib/src/worker/sync/SharedSyncImplementation.js +18 -9
- package/lib/src/worker/sync/WorkerClient.js +3 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/dist/31ba59416bad61e8fb1f.wasm +0 -0
- package/dist/f4ad8bfeb6e6e5326142.wasm +0 -0
package/lib/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/web",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "PowerSync web SDK. Sync Postgres, MongoDB or MySQL with SQLite in your web app",
|
|
5
5
|
"main": "lib/src/index.js",
|
|
6
6
|
"types": "lib/src/index.d.ts",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"author": "JOURNEYAPPS",
|
|
61
61
|
"license": "Apache-2.0",
|
|
62
62
|
"peerDependencies": {
|
|
63
|
-
"@journeyapps/wa-sqlite": "^1.3.
|
|
64
|
-
"@powersync/common": "workspace:^1.
|
|
63
|
+
"@journeyapps/wa-sqlite": "^1.3.2",
|
|
64
|
+
"@powersync/common": "workspace:^1.39.0"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@powersync/common": "workspace:*",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"commander": "^12.1.0"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@journeyapps/wa-sqlite": "^1.3.
|
|
74
|
+
"@journeyapps/wa-sqlite": "^1.3.2",
|
|
75
75
|
"@types/uuid": "^9.0.6",
|
|
76
76
|
"crypto-browserify": "^3.12.0",
|
|
77
77
|
"p-defer": "^4.0.1",
|
|
@@ -8,6 +8,7 @@ export type SharedConnectionWorker = {
|
|
|
8
8
|
export type WrappedWorkerConnectionOptions<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> = {
|
|
9
9
|
baseConnection: AsyncDatabaseConnection;
|
|
10
10
|
identifier: string;
|
|
11
|
+
remoteCanCloseUnexpectedly: boolean;
|
|
11
12
|
/**
|
|
12
13
|
* Need a remote in order to keep a reference to the Proxied worker
|
|
13
14
|
*/
|
|
@@ -21,9 +22,19 @@ export type WrappedWorkerConnectionOptions<Config extends ResolvedWebSQLOpenOpti
|
|
|
21
22
|
export declare class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> implements AsyncDatabaseConnection {
|
|
22
23
|
protected options: WrappedWorkerConnectionOptions<Config>;
|
|
23
24
|
protected lockAbortController: AbortController;
|
|
25
|
+
protected notifyRemoteClosed: AbortController | undefined;
|
|
24
26
|
constructor(options: WrappedWorkerConnectionOptions<Config>);
|
|
25
27
|
protected get baseConnection(): AsyncDatabaseConnection<ResolvedWebSQLOpenOptions>;
|
|
26
28
|
init(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Marks the remote as closed.
|
|
31
|
+
*
|
|
32
|
+
* This can sometimes happen outside of our control, e.g. when a shared worker requests a connection from a tab. When
|
|
33
|
+
* it happens, all methods on the {@link baseConnection} would never resolve. To avoid livelocks in this scenario, we
|
|
34
|
+
* throw on all outstanding promises and forbid new calls.
|
|
35
|
+
*/
|
|
36
|
+
markRemoteClosed(): void;
|
|
37
|
+
private withRemote;
|
|
27
38
|
/**
|
|
28
39
|
* Get a MessagePort which can be used to share the internals of this connection.
|
|
29
40
|
*/
|
|
@@ -5,10 +5,13 @@ import * as Comlink from 'comlink';
|
|
|
5
5
|
*/
|
|
6
6
|
export class WorkerWrappedAsyncDatabaseConnection {
|
|
7
7
|
options;
|
|
8
|
-
lockAbortController;
|
|
8
|
+
lockAbortController = new AbortController();
|
|
9
|
+
notifyRemoteClosed;
|
|
9
10
|
constructor(options) {
|
|
10
11
|
this.options = options;
|
|
11
|
-
|
|
12
|
+
if (options.remoteCanCloseUnexpectedly) {
|
|
13
|
+
this.notifyRemoteClosed = new AbortController();
|
|
14
|
+
}
|
|
12
15
|
}
|
|
13
16
|
get baseConnection() {
|
|
14
17
|
return this.options.baseConnection;
|
|
@@ -16,6 +19,43 @@ export class WorkerWrappedAsyncDatabaseConnection {
|
|
|
16
19
|
init() {
|
|
17
20
|
return this.baseConnection.init();
|
|
18
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Marks the remote as closed.
|
|
24
|
+
*
|
|
25
|
+
* This can sometimes happen outside of our control, e.g. when a shared worker requests a connection from a tab. When
|
|
26
|
+
* it happens, all methods on the {@link baseConnection} would never resolve. To avoid livelocks in this scenario, we
|
|
27
|
+
* throw on all outstanding promises and forbid new calls.
|
|
28
|
+
*/
|
|
29
|
+
markRemoteClosed() {
|
|
30
|
+
// Can non-null assert here because this function is only supposed to be called when remoteCanCloseUnexpectedly was
|
|
31
|
+
// set.
|
|
32
|
+
this.notifyRemoteClosed.abort();
|
|
33
|
+
}
|
|
34
|
+
withRemote(workerPromise) {
|
|
35
|
+
const controller = this.notifyRemoteClosed;
|
|
36
|
+
if (controller) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
if (controller.signal.aborted) {
|
|
39
|
+
reject(new Error('Called operation on closed remote'));
|
|
40
|
+
}
|
|
41
|
+
function handleAbort() {
|
|
42
|
+
reject(new Error('Remote peer closed with request in flight'));
|
|
43
|
+
}
|
|
44
|
+
function completePromise(action) {
|
|
45
|
+
controller.signal.removeEventListener('abort', handleAbort);
|
|
46
|
+
action();
|
|
47
|
+
}
|
|
48
|
+
controller.signal.addEventListener('abort', handleAbort);
|
|
49
|
+
workerPromise()
|
|
50
|
+
.then((data) => completePromise(() => resolve(data)))
|
|
51
|
+
.catch((e) => completePromise(() => reject(e)));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Can't close, so just return the inner worker promise unguarded.
|
|
56
|
+
return workerPromise();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
19
59
|
/**
|
|
20
60
|
* Get a MessagePort which can be used to share the internals of this connection.
|
|
21
61
|
*/
|
|
@@ -66,20 +106,24 @@ export class WorkerWrappedAsyncDatabaseConnection {
|
|
|
66
106
|
async close() {
|
|
67
107
|
// Abort any pending lock requests.
|
|
68
108
|
this.lockAbortController.abort();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
109
|
+
try {
|
|
110
|
+
await this.withRemote(() => this.baseConnection.close());
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
this.options.remote[Comlink.releaseProxy]();
|
|
114
|
+
this.options.onClose?.();
|
|
115
|
+
}
|
|
72
116
|
}
|
|
73
117
|
execute(sql, params) {
|
|
74
|
-
return this.baseConnection.execute(sql, params);
|
|
118
|
+
return this.withRemote(() => this.baseConnection.execute(sql, params));
|
|
75
119
|
}
|
|
76
120
|
executeRaw(sql, params) {
|
|
77
|
-
return this.baseConnection.executeRaw(sql, params);
|
|
121
|
+
return this.withRemote(() => this.baseConnection.executeRaw(sql, params));
|
|
78
122
|
}
|
|
79
123
|
executeBatch(sql, params) {
|
|
80
|
-
return this.baseConnection.executeBatch(sql, params);
|
|
124
|
+
return this.withRemote(() => this.baseConnection.executeBatch(sql, params));
|
|
81
125
|
}
|
|
82
126
|
getConfig() {
|
|
83
|
-
return this.baseConnection.getConfig();
|
|
127
|
+
return this.withRemote(() => this.baseConnection.getConfig());
|
|
84
128
|
}
|
|
85
129
|
}
|
|
@@ -17,6 +17,7 @@ export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
|
|
|
17
17
|
const remote = Comlink.wrap(workerPort);
|
|
18
18
|
return new WorkerWrappedAsyncDatabaseConnection({
|
|
19
19
|
remote,
|
|
20
|
+
remoteCanCloseUnexpectedly: false,
|
|
20
21
|
identifier: options.dbFilename,
|
|
21
22
|
baseConnection: await remote({
|
|
22
23
|
...options,
|
|
@@ -45,6 +45,8 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
|
|
|
45
45
|
const workerDBOpener = Comlink.wrap(workerPort);
|
|
46
46
|
return new WorkerWrappedAsyncDatabaseConnection({
|
|
47
47
|
remote: workerDBOpener,
|
|
48
|
+
// This tab owns the worker, so we're guaranteed to outlive it.
|
|
49
|
+
remoteCanCloseUnexpectedly: false,
|
|
48
50
|
baseConnection: await workerDBOpener({
|
|
49
51
|
dbFilename: this.options.dbFilename,
|
|
50
52
|
vfs,
|
|
@@ -44,6 +44,7 @@ export type WrappedSyncPort = {
|
|
|
44
44
|
clientProvider: Comlink.Remote<AbstractSharedSyncClientProvider>;
|
|
45
45
|
db?: DBAdapter;
|
|
46
46
|
currentSubscriptions: SubscribedStream[];
|
|
47
|
+
closeListeners: (() => void)[];
|
|
47
48
|
};
|
|
48
49
|
/**
|
|
49
50
|
* @internal
|
|
@@ -100,6 +101,7 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
|
|
|
100
101
|
port: MessagePort;
|
|
101
102
|
clientProvider: Comlink.Remote<AbstractSharedSyncClientProvider>;
|
|
102
103
|
currentSubscriptions: never[];
|
|
104
|
+
closeListeners: never[];
|
|
103
105
|
}>;
|
|
104
106
|
/**
|
|
105
107
|
* Removes a message port client from this manager's managed
|
|
@@ -179,7 +179,8 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
179
179
|
const portProvider = {
|
|
180
180
|
port,
|
|
181
181
|
clientProvider: Comlink.wrap(port),
|
|
182
|
-
currentSubscriptions: []
|
|
182
|
+
currentSubscriptions: [],
|
|
183
|
+
closeListeners: []
|
|
183
184
|
};
|
|
184
185
|
this.ports.push(portProvider);
|
|
185
186
|
// Give the newly connected client the latest status
|
|
@@ -226,19 +227,18 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
226
227
|
// We could not find the port to remove
|
|
227
228
|
return () => { };
|
|
228
229
|
}
|
|
230
|
+
for (const closeListener of trackedPort.closeListeners) {
|
|
231
|
+
closeListener();
|
|
232
|
+
}
|
|
229
233
|
if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
234
|
+
// Unconditionally close the connection because the database it's writing to has just been closed.
|
|
235
|
+
await this.connectionManager.disconnect();
|
|
233
236
|
// Clearing the adapter will result in a new one being opened in connect
|
|
234
237
|
this.dbAdapter = null;
|
|
235
238
|
if (shouldReconnect) {
|
|
236
239
|
await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions ?? {});
|
|
237
240
|
}
|
|
238
241
|
}
|
|
239
|
-
if (trackedPort.db) {
|
|
240
|
-
await trackedPort.db.close();
|
|
241
|
-
}
|
|
242
242
|
// Re-index subscriptions, the subscriptions of the removed port would no longer be considered.
|
|
243
243
|
this.collectActiveSubscriptions();
|
|
244
244
|
// Release proxy
|
|
@@ -353,11 +353,20 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
353
353
|
const locked = new LockedAsyncDatabaseAdapter({
|
|
354
354
|
name: identifier,
|
|
355
355
|
openConnection: async () => {
|
|
356
|
-
|
|
356
|
+
const wrapped = new WorkerWrappedAsyncDatabaseConnection({
|
|
357
357
|
remote,
|
|
358
358
|
baseConnection: db,
|
|
359
|
-
identifier
|
|
359
|
+
identifier,
|
|
360
|
+
// It's possible for this worker to outlive the client hosting the database for us. We need to be prepared for
|
|
361
|
+
// that and ensure pending requests are aborted when the tab is closed.
|
|
362
|
+
remoteCanCloseUnexpectedly: true
|
|
363
|
+
});
|
|
364
|
+
lastClient.closeListeners.push(() => {
|
|
365
|
+
this.logger.info('Aborting open connection because associated tab closed.');
|
|
366
|
+
wrapped.close();
|
|
367
|
+
wrapped.markRemoteClosed();
|
|
360
368
|
});
|
|
369
|
+
return wrapped;
|
|
361
370
|
},
|
|
362
371
|
logger: this.logger
|
|
363
372
|
});
|
|
@@ -31,7 +31,9 @@ export class WorkerClient {
|
|
|
31
31
|
}
|
|
32
32
|
async removePort() {
|
|
33
33
|
if (this.resolvedPort) {
|
|
34
|
-
const
|
|
34
|
+
const resolved = this.resolvedPort;
|
|
35
|
+
this.resolvedPort = null;
|
|
36
|
+
const release = await this.sync.removePort(resolved);
|
|
35
37
|
this.resolvedPort = null;
|
|
36
38
|
this.port.postMessage({
|
|
37
39
|
event: SharedSyncClientEvent.CLOSE_ACK,
|