Skip to content

Commit 2b3b44d

Browse files
imrove worker connections
1 parent 1f87766 commit 2b3b44d

File tree

12 files changed

+199
-226
lines changed

12 files changed

+199
-226
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const db = new PowerSyncDatabase({
1717
// dbFilename: 's.sqlite'
1818
// }
1919
database: new WASQLiteOpenFactory({
20-
dbFilename: 'examplsw1se11.db',
20+
dbFilename: 'examplsw1se112.db',
2121
// vfs: WASQLiteVFS.OPFSCoopSyncVFS
2222
vfs: WASQLiteVFS.OPFSCoopSyncVFS //Out of memory errors on iOS Safari
2323
})

packages/web/src/db/PowerSyncDatabase.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
resolveWebSQLFlags,
2323
WebSQLFlags
2424
} from './adapters/web-sql-flags';
25-
import { WorkerDBAdapter } from './adapters/WorkerDBAdapter';
25+
import { WebDBAdapter } from './adapters/WebDBAdapter';
2626
import { SharedWebStreamingSyncImplementation } from './sync/SharedWebStreamingSyncImplementation';
2727
import { SSRStreamingSyncImplementation } from './sync/SSRWebStreamingSyncImplementation';
2828
import { WebRemote } from './sync/WebRemote';
@@ -194,7 +194,7 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
194194
}
195195
return new SharedWebStreamingSyncImplementation({
196196
...syncOptions,
197-
workerDatabase: this.database as WorkerDBAdapter // This should always be the case
197+
db: this.database as WebDBAdapter // This should always be the case
198198
});
199199
default:
200200
return new WebStreamingSyncImplementation(syncOptions);

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
import Logger, { ILogger } from 'js-logger';
1212
import { getNavigatorLocks } from '../..//shared/navigator';
1313
import { AsyncDatabaseConnection } from './AsyncDatabaseConnection';
14+
import { SharedConnectionWorker, WebDBAdapter } from './WebDBAdapter';
15+
import { WorkerWrappedAsyncDatabaseConnection } from './WorkerWrappedAsyncDatabaseConnection';
1416

1517
/**
1618
* @internal
@@ -31,12 +33,14 @@ export type LockedAsyncDatabaseAdapterListener = DBAdapterListener & {
3133
* Wraps a {@link AsyncDatabaseConnection} and provides exclusive locking functions in
3234
* order to implement {@link DBAdapter}.
3335
*/
34-
export class LockedAsyncDatabaseAdapter extends BaseObserver<LockedAsyncDatabaseAdapterListener> implements DBAdapter {
36+
export class LockedAsyncDatabaseAdapter
37+
extends BaseObserver<LockedAsyncDatabaseAdapterListener>
38+
implements WebDBAdapter
39+
{
3540
private logger: ILogger;
3641
private dbGetHelpers: DBGetUtils | null;
3742
private debugMode: boolean;
3843
private _dbIdentifier: string;
39-
private _isInitialized = false;
4044
protected initPromise: Promise<void>;
4145
private _db: AsyncDatabaseConnection | null = null;
4246
protected _disposeTableChangeListener: (() => void) | null = null;
@@ -79,6 +83,13 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver<LockedAsyncDatabase
7983
return this._dbIdentifier;
8084
}
8185

86+
async shareConnection(): Promise<SharedConnectionWorker> {
87+
if (false == this._db instanceof WorkerWrappedAsyncDatabaseConnection) {
88+
throw new Error(`Only worker connections can be shared`);
89+
}
90+
return this._db.shareConnection();
91+
}
92+
8293
/**
8394
* Registers a table change notification callback with the base database.
8495
* This can be extended by custom implementations in order to handle proxy events.
@@ -100,8 +111,6 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver<LockedAsyncDatabase
100111
this._db = await this.options.openConnection();
101112
await this._db.init();
102113
await this.registerOnChangeListener(this._db);
103-
104-
this._isInitialized = true;
105114
this.iterateListeners((cb) => cb.initialized?.());
106115
}
107116

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { DBAdapter } from '@powersync/common';
2+
3+
export type SharedConnectionWorker = {
4+
identifier: string;
5+
port: MessagePort;
6+
};
7+
8+
export interface WebDBAdapter extends DBAdapter {
9+
/**
10+
* Get a MessagePort which can be used to share the internals of this connection.
11+
*/
12+
shareConnection(): Promise<SharedConnectionWorker>;
13+
}

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

Lines changed: 0 additions & 8 deletions
This file was deleted.

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

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as Comlink from 'comlink';
2+
import { AsyncDatabaseConnection, OnTableChangeCallback, ProxiedQueryResult } from './AsyncDatabaseConnection';
3+
4+
export type SharedConnectionWorker = {
5+
identifier: string;
6+
port: MessagePort;
7+
};
8+
9+
export type WrappedWorkerConnectionOptions = {
10+
baseConnection: AsyncDatabaseConnection;
11+
identifier: string;
12+
worker: Worker | MessagePort;
13+
};
14+
15+
/**
16+
* Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
17+
* functions for worker listeners.
18+
*/
19+
export class WorkerWrappedAsyncDatabaseConnection implements AsyncDatabaseConnection {
20+
constructor(protected options: WrappedWorkerConnectionOptions) {}
21+
22+
protected get baseConnection() {
23+
return this.options.baseConnection;
24+
}
25+
26+
init(): Promise<void> {
27+
return this.baseConnection.init();
28+
}
29+
30+
/**
31+
* Get a MessagePort which can be used to share the internals of this connection.
32+
*/
33+
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+
}
43+
44+
return {
45+
identifier: identifier,
46+
port: worker
47+
};
48+
}
49+
50+
/**
51+
* Registers a table change notification callback with the base database.
52+
* This can be extended by custom implementations in order to handle proxy events.
53+
*/
54+
async registerOnTableChange(callback: OnTableChangeCallback) {
55+
return this.baseConnection.registerOnTableChange(Comlink.proxy(callback));
56+
}
57+
58+
close(): Promise<void> {
59+
return this.baseConnection.close();
60+
}
61+
62+
execute(sql: string, params?: any[]): Promise<ProxiedQueryResult> {
63+
return this.baseConnection.execute(sql, params);
64+
}
65+
66+
executeBatch(sql: string, params?: any[]): Promise<ProxiedQueryResult> {
67+
return this.baseConnection.executeBatch(sql, params);
68+
}
69+
}
Lines changed: 30 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { type PowerSyncOpenFactoryOptions } from '@powersync/common';
2+
import * as Comlink from 'comlink';
3+
import { OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection';
4+
import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter';
25
import { ResolvedWebSQLOpenOptions, WebSQLFlags } from '../web-sql-flags';
36
import { WASQLiteVFS } from './WASQLiteConnection';
7+
import { WASQLiteOpenFactory } from './WASQLiteOpenFactory';
48

59
/**
610
* These flags are the same as {@link WebSQLFlags}.
@@ -25,96 +29,29 @@ export interface WASQLiteDBAdapterOptions extends Omit<PowerSyncOpenFactoryOptio
2529
/**
2630
* Adapter for WA-SQLite SQLite connections.
2731
*/
28-
// FIXME
29-
// export class WASQLiteDBAdapter extends BaseObserver<DBAdapterListener> implements DBAdapter {
30-
// private initialized: Promise<void>;
31-
// private logger: ILogger;
32-
// private dbGetHelpers: DBGetUtils | null;
33-
// private methods: DBFunctionsInterface | null;
34-
// private debugMode: boolean;
35-
36-
// constructor(protected options: WASQLiteDBAdapterOptions) {
37-
// super();
38-
// this.logger = Logger.get('WASQLite');
39-
// this.dbGetHelpers = null;
40-
// this.methods = null;
41-
// this.debugMode = options.debugMode ?? false;
42-
// if (this.debugMode) {
43-
// const originalExecute = this._execute.bind(this);
44-
// this._execute = async (sql, bindings) => {
45-
// const start = performance.now();
46-
// try {
47-
// const r = await originalExecute(sql, bindings);
48-
// performance.measure(`[SQL] ${sql}`, { start });
49-
// return r;
50-
// } catch (e: any) {
51-
// performance.measure(`[SQL] [ERROR: ${e.message}] ${sql}`, { start });
52-
// throw e;
53-
// }
54-
// };
55-
// }
56-
// this.initialized = this.init();
57-
// this.dbGetHelpers = this.generateDBHelpers({
58-
// execute: (query, params) => this.acquireLock(() => this._execute(query, params))
59-
// });
60-
// }
61-
62-
// get name() {
63-
// return this.options.dbFilename;
64-
// }
65-
66-
// protected get flags(): Required<WASQLiteFlags> {
67-
// return resolveWebSQLFlags(this.options.flags ?? {});
68-
// }
69-
70-
// getWorker() {}
71-
72-
// protected async init() {
73-
// const { enableMultiTabs, useWebWorker } = this.flags;
74-
// if (!enableMultiTabs) {
75-
// this.logger.warn('Multiple tabs are not enabled in this browser');
76-
// }
77-
78-
// if (useWebWorker) {
79-
// const optionsDbWorker = this.options.worker;
80-
81-
// const dbOpener = this.options.workerPort
82-
// ? Comlink.wrap<OpenDB>(this.options.workerPort)
83-
// : typeof optionsDbWorker === 'function'
84-
// ? Comlink.wrap<OpenDB>(
85-
// resolveWorkerDatabasePortFactory(() =>
86-
// optionsDbWorker({
87-
// ...this.options,
88-
// flags: this.flags
89-
// })
90-
// )
91-
// )
92-
// : getWorkerDatabaseOpener(this.options.dbFilename, enableMultiTabs, optionsDbWorker);
93-
94-
// this.methods = await dbOpener({
95-
// dbFileName: this.options.dbFilename,
96-
// vfs: this.options.vfs
97-
// });
98-
// this.methods.registerOnTableChange(
99-
// Comlink.proxy((event) => {
100-
// this.iterateListeners((cb) => cb.tablesUpdated?.(event));
101-
// })
102-
// );
103-
104-
// return;
105-
// }
106-
107-
// // Not using a worker
108-
// const connection = new WASqliteConnection({
109-
// dbFileName: this.options.dbFilename
110-
// });
111-
// await connection.init();
112-
113-
// this.methods = connection;
114-
// this.methods.registerOnTableChange((event) => {
115-
// this.iterateListeners((cb) => cb.tablesUpdated?.(event));
116-
// });
117-
// }
118-
119-
// async refreshSchema(): Promise<void> {}
120-
// }
32+
export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
33+
constructor(options: WASQLiteDBAdapterOptions) {
34+
super({
35+
name: options.dbFilename,
36+
openConnection: async () => {
37+
const { workerPort } = options;
38+
if (workerPort) {
39+
const wrapped = Comlink.wrap<OpenAsyncDatabaseConnection>(workerPort);
40+
return wrapped(options);
41+
}
42+
const openFactory = new WASQLiteOpenFactory({
43+
dbFilename: options.dbFilename,
44+
dbLocation: options.dbLocation,
45+
debugMode: options.debugMode,
46+
flags: options.flags,
47+
logger: options.logger,
48+
vfs: options.vfs,
49+
worker: options.worker
50+
});
51+
return openFactory.openConnection();
52+
},
53+
debugMode: options.debugMode,
54+
logger: options.logger
55+
});
56+
}
57+
}

0 commit comments

Comments
 (0)