Skip to content

Commit 446a93c

Browse files
fix worker proxies
1 parent 2b3b44d commit 446a93c

File tree

10 files changed

+99
-79
lines changed

10 files changed

+99
-79
lines changed

demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AppSchema } from '@/library/powersync/AppSchema';
33
import { SupabaseConnector } from '@/library/powersync/SupabaseConnector';
44
import { CircularProgress } from '@mui/material';
55
import { PowerSyncContext } from '@powersync/react';
6-
import { PowerSyncDatabase, WASQLiteOpenFactory, WASQLiteVFS } from '@powersync/web';
6+
import { PowerSyncDatabase } from '@powersync/web';
77
import Logger from 'js-logger';
88
import React, { Suspense } from 'react';
99
import { NavigationPanelContextProvider } from '../navigation/NavigationPanelContext';
@@ -13,14 +13,17 @@ export const useSupabase = () => React.useContext(SupabaseContext);
1313

1414
export const db = new PowerSyncDatabase({
1515
schema: AppSchema,
16-
// database: {
17-
// dbFilename: 's.sqlite'
18-
// }
19-
database: new WASQLiteOpenFactory({
20-
dbFilename: 'examplsw1se112.db',
21-
// vfs: WASQLiteVFS.OPFSCoopSyncVFS
22-
vfs: WASQLiteVFS.OPFSCoopSyncVFS //Out of memory errors on iOS Safari
23-
})
16+
database: {
17+
dbFilename: 's.sqlite'
18+
},
19+
flags: {
20+
enableMultiTabs: true
21+
}
22+
// database: new WASQLiteOpenFactory({
23+
// dbFilename: 'examplsw1se112.db'
24+
// // vfs: WASQLiteVFS.OPFSCoopSyncVFS
25+
// // vfs: WASQLiteVFS.OPFSCoopSyncVFS //Out of memory errors on iOS Safari
26+
// })
2427
});
2528

2629
export const SystemProvider = ({ children }: { children: React.ReactNode }) => {

packages/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"@powersync/common": "workspace:*",
6868
"async-mutex": "^0.4.0",
6969
"bson": "^6.6.0",
70-
"comlink": "^4.4.1",
70+
"comlink": "^4.4.2",
7171
"commander": "^12.1.0",
7272
"js-logger": "^1.6.1"
7373
},

packages/web/src/db/PowerSyncDatabase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export const DEFAULT_POWERSYNC_FLAGS: Required<WebPowerSyncFlags> = {
7373
externallyUnload: false
7474
};
7575

76-
export const resolveWebPowerSyncFlags = (flags?: WebPowerSyncFlags): WebPowerSyncFlags => {
76+
export const resolveWebPowerSyncFlags = (flags?: WebPowerSyncFlags): Required<WebPowerSyncFlags> => {
7777
return {
7878
...DEFAULT_POWERSYNC_FLAGS,
7979
...flags,

packages/web/src/db/adapters/AsyncDatabaseConnection.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { BatchedUpdateNotification, QueryResult, SQLOpenOptions } from '@powersync/common';
1+
import { BatchedUpdateNotification, QueryResult } from '@powersync/common';
2+
import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
23

34
/**
45
* Proxied query result does not contain a function for accessing row values
@@ -24,6 +25,6 @@ export interface AsyncDatabaseConnection {
2425
registerOnTableChange(callback: OnTableChangeCallback): Promise<() => void>;
2526
}
2627

27-
export type OpenAsyncDatabaseConnection<Options extends SQLOpenOptions = SQLOpenOptions> = (
28+
export type OpenAsyncDatabaseConnection<Options extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> = (
2829
options: Options
2930
) => AsyncDatabaseConnection;

packages/web/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
11
import * as Comlink from 'comlink';
2-
import { AsyncDatabaseConnection, OnTableChangeCallback, ProxiedQueryResult } from './AsyncDatabaseConnection';
2+
import {
3+
AsyncDatabaseConnection,
4+
OnTableChangeCallback,
5+
OpenAsyncDatabaseConnection,
6+
ProxiedQueryResult
7+
} from './AsyncDatabaseConnection';
8+
import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
39

410
export type SharedConnectionWorker = {
511
identifier: string;
612
port: MessagePort;
713
};
814

9-
export type WrappedWorkerConnectionOptions = {
15+
export type WrappedWorkerConnectionOptions<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> = {
1016
baseConnection: AsyncDatabaseConnection;
1117
identifier: string;
12-
worker: Worker | MessagePort;
18+
/**
19+
* Need a remote in order to keep a reference to the Proxied worker
20+
*/
21+
remote: Comlink.Remote<OpenAsyncDatabaseConnection<Config>>;
1322
};
1423

1524
/**
1625
* Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
1726
* functions for worker listeners.
1827
*/
19-
export class WorkerWrappedAsyncDatabaseConnection implements AsyncDatabaseConnection {
20-
constructor(protected options: WrappedWorkerConnectionOptions) {}
28+
export class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions>
29+
implements AsyncDatabaseConnection
30+
{
31+
constructor(protected options: WrappedWorkerConnectionOptions<Config>) {}
2132

2233
protected get baseConnection() {
2334
return this.options.baseConnection;
@@ -31,20 +42,10 @@ export class WorkerWrappedAsyncDatabaseConnection implements AsyncDatabaseConnec
3142
* Get a MessagePort which can be used to share the internals of this connection.
3243
*/
3344
async shareConnection(): Promise<SharedConnectionWorker> {
34-
const { identifier, worker } = this.options;
35-
if (worker instanceof Worker) {
36-
// We can't transfer a Worker instance, need a MessagePort
37-
// Comlink provides a nice utility for exposing a MessagePort
38-
// from a Worker
39-
const temp = Comlink.wrap(worker);
40-
const newPort = await temp[Comlink.createEndpoint]();
41-
return { port: newPort, identifier };
42-
}
45+
const { identifier, remote } = this.options;
4346

44-
return {
45-
identifier: identifier,
46-
port: worker
47-
};
47+
const newPort = await remote[Comlink.createEndpoint]();
48+
return { port: newPort, identifier };
4849
}
4950

5051
/**
@@ -55,8 +56,9 @@ export class WorkerWrappedAsyncDatabaseConnection implements AsyncDatabaseConnec
5556
return this.baseConnection.registerOnTableChange(Comlink.proxy(callback));
5657
}
5758

58-
close(): Promise<void> {
59-
return this.baseConnection.close();
59+
async close(): Promise<void> {
60+
await this.baseConnection.close();
61+
this.options.remote[Comlink.releaseProxy]();
6062
}
6163

6264
execute(sql: string, params?: any[]): Promise<ProxiedQueryResult> {

packages/web/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { type PowerSyncOpenFactoryOptions } from '@powersync/common';
22
import * as Comlink from 'comlink';
3+
import { resolveWebPowerSyncFlags } from '../../PowerSyncDatabase';
34
import { OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection';
45
import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter';
56
import { ResolvedWebSQLOpenOptions, WebSQLFlags } from '../web-sql-flags';
7+
import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection';
68
import { WASQLiteVFS } from './WASQLiteConnection';
79
import { WASQLiteOpenFactory } from './WASQLiteOpenFactory';
810

@@ -36,8 +38,12 @@ export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
3638
openConnection: async () => {
3739
const { workerPort } = options;
3840
if (workerPort) {
39-
const wrapped = Comlink.wrap<OpenAsyncDatabaseConnection>(workerPort);
40-
return wrapped(options);
41+
const remote = Comlink.wrap<OpenAsyncDatabaseConnection>(workerPort);
42+
return new WorkerWrappedAsyncDatabaseConnection({
43+
remote,
44+
identifier: options.dbFilename,
45+
baseConnection: await remote({ ...options, flags: resolveWebPowerSyncFlags(options.flags) })
46+
});
4147
}
4248
const openFactory = new WASQLiteOpenFactory({
4349
dbFilename: options.dbFilename,

packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ import { openWorkerDatabasePort, resolveWorkerDatabasePortFactory } from '../../
44
import { AbstractWebSQLOpenFactory } from '../AbstractWebSQLOpenFactory';
55
import { AsyncDatabaseConnection, OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection';
66
import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter';
7-
import { WebSQLOpenFactoryOptions } from '../web-sql-flags';
7+
import { ResolvedWebSQLOpenOptions, WebSQLOpenFactoryOptions } from '../web-sql-flags';
88
import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection';
9-
import { WASqliteConnection, WASQLiteOpenOptions, WASQLiteVFS } from './WASQLiteConnection';
9+
import { WASqliteConnection, WASQLiteVFS } from './WASQLiteConnection';
1010

1111
export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions {
1212
vfs?: WASQLiteVFS;
1313
}
1414

15+
export interface ResolvedWASQLiteOpenFactoryOptions extends ResolvedWebSQLOpenOptions {
16+
vfs: WASQLiteVFS;
17+
}
1518
/**
1619
* Opens a SQLite connection using WA-SQLite.
1720
*/
@@ -36,6 +39,8 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
3639

3740
async openConnection(): Promise<AsyncDatabaseConnection> {
3841
const { enableMultiTabs, useWebWorker } = this.resolvedFlags;
42+
const { vfs = WASQLiteVFS.IDBBatchAtomicVFS } = this.waOptions;
43+
3944
if (!enableMultiTabs) {
4045
this.logger.warn('Multiple tabs are not enabled in this browser');
4146
}
@@ -53,24 +58,24 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
5358
)
5459
: openWorkerDatabasePort(this.options.dbFilename, enableMultiTabs, optionsDbWorker, this.waOptions.vfs);
5560

56-
const workerDBOpener = Comlink.wrap<OpenAsyncDatabaseConnection<WASQLiteOpenOptions>>(workerPort);
61+
const workerDBOpener = Comlink.wrap<OpenAsyncDatabaseConnection<ResolvedWASQLiteOpenFactoryOptions>>(workerPort);
5762

5863
return new WorkerWrappedAsyncDatabaseConnection({
64+
remote: workerDBOpener,
5965
baseConnection: await workerDBOpener({
6066
dbFilename: this.options.dbFilename,
61-
vfs: this.waOptions.vfs,
67+
vfs,
6268
flags: this.resolvedFlags
6369
}),
64-
identifier: this.options.dbFilename,
65-
worker: workerPort
70+
identifier: this.options.dbFilename
6671
});
6772
} else {
6873
// Don't use a web worker
6974
return new WASqliteConnection({
7075
dbFilename: this.options.dbFilename,
7176
dbLocation: this.options.dbLocation,
7277
debugMode: this.options.debugMode,
73-
vfs: this.waOptions.vfs,
78+
vfs,
7479
flags: this.resolvedFlags
7580
});
7681
}

packages/web/src/worker/db/WASQLiteDB.worker.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ let nextClientId = 1;
2525
const openWorkerConnection = async (options: WASQLiteOpenOptions): Promise<AsyncDatabaseConnection> => {
2626
const connection = new WASqliteConnection(options);
2727
return {
28-
init: () => connection.init(),
29-
close: () => connection.close(),
30-
execute: async (sql: string, params?: any[]) => connection.execute(sql, params),
31-
executeBatch: async (sql: string, params?: any[]) => connection.executeBatch(sql, params),
32-
registerOnTableChange: async (callback) => {
28+
init: Comlink.proxy(() => connection.init()),
29+
close: Comlink.proxy(() => connection.close()),
30+
execute: Comlink.proxy(async (sql: string, params?: any[]) => connection.execute(sql, params)),
31+
executeBatch: Comlink.proxy(async (sql: string, params?: any[]) => connection.executeBatch(sql, params)),
32+
registerOnTableChange: Comlink.proxy(async (callback) => {
3333
// Proxy the callback remove function
3434
return Comlink.proxy(await connection.registerOnTableChange(callback));
35-
}
35+
})
3636
};
3737
};
3838

@@ -57,8 +57,12 @@ const openDBShared = async (options: WASQLiteOpenOptions): Promise<AsyncDatabase
5757

5858
const wrappedConnection = {
5959
...db,
60+
init: Comlink.proxy(() => {
61+
// the init has been done automatically
62+
}),
6063
close: Comlink.proxy(() => {
6164
const { clientIds } = dbEntry;
65+
console.debug(`Close requested from client ${clientId} of ${[...clientIds]}`);
6266
clientIds.delete(clientId);
6367
if (clientIds.size == 0) {
6468
console.debug(`Closing connection to ${dbFilename}.`);
@@ -82,14 +86,14 @@ if (typeof SharedWorkerGlobalScope !== 'undefined') {
8286
console.debug('Exposing shared db on port', port);
8387
Comlink.expose(openDBShared, port);
8488
};
85-
86-
addEventListener('unload', () => {
87-
Array.from(DBMap.values()).forEach(async (dbConnection) => {
88-
const db = await dbConnection.db;
89-
db.close?.();
90-
});
91-
});
9289
} else {
9390
// A dedicated worker can be shared externally
9491
Comlink.expose(openDBShared);
9592
}
93+
94+
addEventListener('unload', () => {
95+
Array.from(DBMap.values()).forEach(async (dbConnection) => {
96+
const { db } = dbConnection;
97+
db.close?.();
98+
});
99+
});

packages/web/src/worker/sync/SharedSyncImplementation.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,12 @@ export class SharedSyncImplementation
159159
// TODO share logic here
160160
const lastClient = this.ports[this.ports.length - 1];
161161
const workerPort = await lastClient.clientProvider.getDBWorkerPort();
162+
const remote = Comlink.wrap<OpenAsyncDatabaseConnection<WASQLiteOpenOptions>>(workerPort);
162163
const locked = new LockedAsyncDatabaseAdapter({
163164
name: this.syncParams!.dbName,
164165
openConnection: async () => {
165-
const remote = Comlink.wrap<OpenAsyncDatabaseConnection<WASQLiteOpenOptions>>(workerPort);
166166
return new WorkerWrappedAsyncDatabaseConnection({
167+
remote,
167168
baseConnection: await remote({
168169
dbFilename: this.syncParams!.dbName,
169170
// TODO improve
@@ -175,8 +176,7 @@ export class SharedSyncImplementation
175176
ssrMode: false
176177
}
177178
}),
178-
identifier: this.syncParams!.dbName,
179-
worker: workerPort
179+
identifier: this.syncParams!.dbName
180180
});
181181
},
182182
logger: this.logger
@@ -273,30 +273,29 @@ export class SharedSyncImplementation
273273
});
274274

275275
if (this.dbAdapter == trackedPort.db && this.syncStreamClient) {
276-
this.dbAdapter!.close();
277-
278276
await this.disconnect();
279277
// Ask for a new DB worker port handler
280278
const lastClient = this.ports[this.ports.length - 1];
281279
const workerPort = await lastClient.clientProvider.getDBWorkerPort();
280+
const remote = Comlink.wrap<OpenAsyncDatabaseConnection<WASQLiteOpenOptions>>(workerPort);
281+
const db = await remote({
282+
dbFilename: this.syncParams!.dbName,
283+
// TODO improve
284+
flags: {
285+
enableMultiTabs: true,
286+
useWebWorker: true,
287+
broadcastLogs: true,
288+
disableSSRWarning: true,
289+
ssrMode: false
290+
}
291+
});
282292
const locked = new LockedAsyncDatabaseAdapter({
283293
name: this.syncParams!.dbName,
284294
openConnection: async () => {
285-
const remote = Comlink.wrap<OpenAsyncDatabaseConnection<WASQLiteOpenOptions>>(workerPort);
286295
return new WorkerWrappedAsyncDatabaseConnection({
287-
baseConnection: await remote({
288-
dbFilename: this.syncParams!.dbName,
289-
// TODO improve
290-
flags: {
291-
enableMultiTabs: true,
292-
useWebWorker: true,
293-
broadcastLogs: true,
294-
disableSSRWarning: true,
295-
ssrMode: false
296-
}
297-
}),
298-
identifier: this.syncParams!.dbName,
299-
worker: workerPort
296+
remote,
297+
baseConnection: db,
298+
identifier: this.syncParams!.dbName
300299
});
301300
},
302301
logger: this.logger

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)