Skip to content

Commit 7943626

Browse files
DominicGBauerDominicGBauer
andauthored
feat: allow for web sdk to be used without web worker (#178)
Co-authored-by: DominicGBauer <[email protected]>
1 parent 3a584a0 commit 7943626

21 files changed

+276
-203
lines changed

.changeset/rotten-cherries-reflect.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/web': minor
3+
---
4+
5+
Allow package to be used without web workers

packages/web/src/db/PowerSyncDatabase.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
import {
2+
type AbstractStreamingSyncImplementation,
3+
type PowerSyncBackendConnector,
4+
type BucketStorageAdapter,
5+
type PowerSyncDatabaseOptions,
6+
type PowerSyncCloseOptions,
7+
type PowerSyncConnectionOptions,
28
AbstractPowerSyncDatabase,
3-
AbstractStreamingSyncImplementation,
4-
PowerSyncBackendConnector,
59
SqliteBucketStorage,
6-
BucketStorageAdapter,
7-
PowerSyncDatabaseOptions,
8-
PowerSyncCloseOptions,
9-
DEFAULT_POWERSYNC_CLOSE_OPTIONS,
10-
PowerSyncConnectionOptions
10+
DEFAULT_POWERSYNC_CLOSE_OPTIONS
1111
} from '@powersync/common';
12-
12+
import { Mutex } from 'async-mutex';
1313
import { WebRemote } from './sync/WebRemote';
1414
import { SharedWebStreamingSyncImplementation } from './sync/SharedWebStreamingSyncImplementation';
1515
import { SSRStreamingSyncImplementation } from './sync/SSRWebStreamingSyncImplementation';
1616
import {
1717
WebStreamingSyncImplementation,
1818
WebStreamingSyncImplementationOptions
1919
} from './sync/WebStreamingSyncImplementation';
20-
import { Mutex } from 'async-mutex';
2120

2221
export interface WebPowerSyncFlags {
2322
/**
2423
* Enables multi tab support
2524
*/
2625
enableMultiTabs?: boolean;
26+
useWebWorker?: boolean;
2727
/**
2828
* Open in SSR placeholder mode. DB operations and Sync operations will be a No-op
2929
*/
@@ -126,8 +126,8 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
126126
case flags?.enableMultiTabs:
127127
if (!flags?.broadcastLogs) {
128128
const warning = `
129-
Multiple tabs are enabled, but broadcasting of logs is disabled.
130-
Logs for shared sync worker will only be available in the shared worker context
129+
Multiple tabs are enabled, but broadcasting of logs is disabled.
130+
Logs for shared sync worker will only be available in the shared worker context
131131
`;
132132
const logger = this.options.logger;
133133
logger ? logger.warn(warning) : console.warn(warning);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface WebPowerSyncOpenFactoryOptions extends PowerSyncOpenFactoryOpti
1616
}
1717

1818
export const DEFAULT_POWERSYNC_FLAGS: WebPowerSyncOpenFlags = {
19+
useWebWorker: true,
1920
/**
2021
* Multiple tabs are by default not supported on Android, iOS and Safari.
2122
* Other platforms will have multiple tabs enabled by default.
@@ -80,6 +81,9 @@ export abstract class AbstractWebPowerSyncDatabaseOpenFactory extends AbstractPo
8081
if (typeof this.options.flags?.enableMultiTabs != 'undefined') {
8182
flags.enableMultiTabs = this.options.flags.enableMultiTabs;
8283
}
84+
if (flags.useWebWorker === false) {
85+
flags.enableMultiTabs = false;
86+
}
8387
return flags;
8488
}
8589

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

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import {
2-
BaseObserver,
3-
DBAdapter,
4-
DBAdapterListener,
5-
DBGetUtils,
6-
DBLockOptions,
7-
LockContext,
8-
PowerSyncOpenFactoryOptions,
9-
QueryResult,
10-
Transaction
2+
type DBAdapter,
3+
type DBAdapterListener,
4+
type DBGetUtils,
5+
type DBLockOptions,
6+
type LockContext,
7+
type PowerSyncOpenFactoryOptions,
8+
type QueryResult,
9+
type Transaction,
10+
BaseObserver
1111
} from '@powersync/common';
1212
import * as Comlink from 'comlink';
13-
import Logger, { ILogger } from 'js-logger';
14-
import type { DBWorkerInterface, OpenDB } from '../../../worker/db/open-db';
13+
import Logger, { type ILogger } from 'js-logger';
14+
import type { DBFunctionsInterface, OpenDB } from '../../../shared/types';
15+
import { _openDB } from '../../../shared/open-db';
1516
import { getWorkerDatabaseOpener } from '../../../worker/db/open-worker-database';
1617

1718
export type WASQLiteFlags = {
1819
enableMultiTabs?: boolean;
20+
useWebWorker?: boolean;
1921
};
2022

2123
export interface WASQLiteDBAdapterOptions extends Omit<PowerSyncOpenFactoryOptions, 'schema'> {
@@ -34,13 +36,13 @@ export class WASQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
3436
private initialized: Promise<void>;
3537
private logger: ILogger;
3638
private dbGetHelpers: DBGetUtils | null;
37-
private workerMethods: DBWorkerInterface | null;
39+
private methods: DBFunctionsInterface | null;
3840

3941
constructor(protected options: WASQLiteDBAdapterOptions) {
4042
super();
4143
this.logger = Logger.get('WASQLite');
4244
this.dbGetHelpers = null;
43-
this.workerMethods = null;
45+
this.methods = null;
4446
this.initialized = this.init();
4547
this.dbGetHelpers = this.generateDBHelpers({ execute: this._execute.bind(this) });
4648
}
@@ -56,22 +58,31 @@ export class WASQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
5658
getWorker() {}
5759

5860
protected async init() {
59-
const { enableMultiTabs } = this.flags;
61+
const { enableMultiTabs, useWebWorker } = this.flags;
6062
if (!enableMultiTabs) {
6163
this.logger.warn('Multiple tabs are not enabled in this browser');
6264
}
6365

64-
const dbOpener = this.options.workerPort
65-
? Comlink.wrap<OpenDB>(this.options.workerPort)
66-
: getWorkerDatabaseOpener(this.options.dbFilename, enableMultiTabs);
66+
if (useWebWorker) {
67+
const dbOpener = this.options.workerPort
68+
? Comlink.wrap<OpenDB>(this.options.workerPort)
69+
: getWorkerDatabaseOpener(this.options.dbFilename, enableMultiTabs);
6770

68-
this.workerMethods = await dbOpener(this.options.dbFilename);
71+
this.methods = await dbOpener(this.options.dbFilename);
6972

70-
this.workerMethods.registerOnTableChange(
71-
Comlink.proxy((opType: number, tableName: string, rowId: number) => {
72-
this.iterateListeners((cb) => cb.tablesUpdated?.({ opType, table: tableName, rowId }));
73-
})
74-
);
73+
this.methods!.registerOnTableChange(
74+
Comlink.proxy((opType: number, tableName: string, rowId: number) => {
75+
this.iterateListeners((cb) => cb.tablesUpdated?.({ opType, table: tableName, rowId }));
76+
})
77+
);
78+
79+
return;
80+
}
81+
this.methods = await _openDB(this.options.dbFilename, { useWebWorker: false });
82+
83+
this.methods.registerOnTableChange((opType: number, tableName: string, rowId: number) => {
84+
this.iterateListeners((cb) => cb.tablesUpdated?.({ opType, table: tableName, rowId }));
85+
});
7586
}
7687

7788
async execute(query: string, params?: any[] | undefined): Promise<QueryResult> {
@@ -87,7 +98,7 @@ export class WASQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
8798
*/
8899
private _execute = async (sql: string, bindings?: any[]): Promise<QueryResult> => {
89100
await this.initialized;
90-
const result = await this.workerMethods!.execute!(sql, bindings);
101+
const result = await this.methods!.execute!(sql, bindings);
91102
return {
92103
...result,
93104
rows: {
@@ -102,7 +113,7 @@ export class WASQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
102113
*/
103114
private _executeBatch = async (query: string, params?: any[]): Promise<QueryResult> => {
104115
await this.initialized;
105-
const result = await this.workerMethods!.executeBatch!(query, params);
116+
const result = await this.methods!.executeBatch!(query, params);
106117
return {
107118
...result,
108119
rows: undefined
@@ -115,7 +126,7 @@ export class WASQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
115126
* tabs are still using it.
116127
*/
117128
close() {
118-
this.workerMethods?.close?.();
129+
this.methods?.close?.();
119130
}
120131

121132
async getAll<T>(sql: string, parameters?: any[] | undefined): Promise<T[]> {

packages/web/src/worker/db/open-db.ts renamed to packages/web/src/shared/open-db.ts

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,14 @@
11
import * as SQLite from '@journeyapps/wa-sqlite';
22
import '@journeyapps/wa-sqlite';
33
import * as Comlink from 'comlink';
4-
import { QueryResult } from '@powersync/common';
5-
6-
export type WASQLExecuteResult = Omit<QueryResult, 'rows'> & {
7-
rows: {
8-
_array: any[];
9-
length: number;
10-
};
11-
};
12-
13-
export type DBWorkerInterface = {
14-
// Close is only exposed when used in a single non shared webworker
15-
close?: () => void;
16-
execute: WASQLiteExecuteMethod;
17-
executeBatch: WASQLiteExecuteBatchMethod;
18-
registerOnTableChange: (callback: OnTableChangeCallback) => void;
19-
};
20-
21-
export type WASQLiteExecuteMethod = (sql: string, params?: any[]) => Promise<WASQLExecuteResult>;
22-
export type WASQLiteExecuteBatchMethod = (sql: string, params?: any[]) => Promise<WASQLExecuteResult>;
23-
export type OnTableChangeCallback = (opType: number, tableName: string, rowId: number) => void;
24-
export type OpenDB = (dbFileName: string) => DBWorkerInterface;
25-
26-
export type SQLBatchTuple = [string] | [string, Array<any> | Array<Array<any>>];
4+
import type { DBFunctionsInterface, OnTableChangeCallback, WASQLExecuteResult } from './types';
275

286
let nextId = 1;
297

30-
export async function _openDB(dbFileName: string): Promise<DBWorkerInterface> {
8+
export async function _openDB(
9+
dbFileName: string,
10+
options: { useWebWorker: boolean } = { useWebWorker: true }
11+
): Promise<DBFunctionsInterface> {
3112
const { default: moduleFactory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite-async.mjs');
3213
const module = await moduleFactory();
3314
const sqlite3 = SQLite.Factory(module);
@@ -47,14 +28,6 @@ export async function _openDB(dbFileName: string): Promise<DBWorkerInterface> {
4728
Array.from(listeners.values()).forEach((l) => l(opType, tableName, rowId));
4829
});
4930

50-
const registerOnTableChange = (callback: OnTableChangeCallback) => {
51-
const id = nextId++;
52-
listeners.set(id, callback);
53-
return Comlink.proxy(() => {
54-
listeners.delete(id);
55-
});
56-
};
57-
5831
/**
5932
* This executes single SQL statements inside a requested lock.
6033
*/
@@ -198,12 +171,37 @@ export async function _openDB(dbFileName: string): Promise<DBWorkerInterface> {
198171
});
199172
};
200173

174+
if (options.useWebWorker) {
175+
const registerOnTableChange = (callback: OnTableChangeCallback) => {
176+
const id = nextId++;
177+
listeners.set(id, callback);
178+
return Comlink.proxy(() => {
179+
listeners.delete(id);
180+
});
181+
};
182+
183+
return {
184+
execute: Comlink.proxy(execute),
185+
executeBatch: Comlink.proxy(executeBatch),
186+
registerOnTableChange: Comlink.proxy(registerOnTableChange),
187+
close: Comlink.proxy(() => {
188+
sqlite3.close(db);
189+
})
190+
};
191+
}
192+
193+
const registerOnTableChange = (callback: OnTableChangeCallback) => {
194+
const id = nextId++;
195+
listeners.set(id, callback);
196+
return () => {
197+
listeners.delete(id);
198+
};
199+
};
200+
201201
return {
202-
execute: Comlink.proxy(execute),
203-
executeBatch: Comlink.proxy(executeBatch),
204-
registerOnTableChange: Comlink.proxy(registerOnTableChange),
205-
close: Comlink.proxy(() => {
206-
sqlite3.close(db);
207-
})
202+
execute: execute,
203+
executeBatch: executeBatch,
204+
registerOnTableChange: registerOnTableChange,
205+
close: () => sqlite3.close(db)
208206
};
209207
}

packages/web/src/shared/types.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { QueryResult } from '@powersync/common';
2+
3+
export type WASQLExecuteResult = Omit<QueryResult, 'rows'> & {
4+
rows: {
5+
_array: any[];
6+
length: number;
7+
};
8+
};
9+
10+
export type DBFunctionsInterface = {
11+
// Close is only exposed when used in a single non shared webworker
12+
close?: () => void;
13+
execute: WASQLiteExecuteMethod;
14+
executeBatch: WASQLiteExecuteBatchMethod;
15+
registerOnTableChange: (callback: OnTableChangeCallback) => void;
16+
};
17+
18+
/**
19+
* @deprecated use [DBFunctionsInterface instead]
20+
*/
21+
export type DBWorkerInterface = DBFunctionsInterface;
22+
23+
export type WASQLiteExecuteMethod = (sql: string, params?: any[]) => Promise<WASQLExecuteResult>;
24+
export type WASQLiteExecuteBatchMethod = (sql: string, params?: any[]) => Promise<WASQLExecuteResult>;
25+
export type OnTableChangeCallback = (opType: number, tableName: string, rowId: number) => void;
26+
export type OpenDB = (dbFileName: string) => DBWorkerInterface;
27+
28+
export type SQLBatchTuple = [string] | [string, Array<any> | Array<Array<any>>];

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import '@journeyapps/wa-sqlite';
2-
32
import * as Comlink from 'comlink';
4-
5-
import { DBWorkerInterface, _openDB } from './open-db';
3+
import { _openDB } from '../../shared/open-db';
4+
import type { DBFunctionsInterface } from '../../shared/types';
65

76
/**
87
* Keeps track of open DB connections and the clients which
98
* are using it.
109
*/
1110
type SharedDBWorkerConnection = {
1211
clientIds: Set<number>;
13-
db: DBWorkerInterface;
12+
db: DBFunctionsInterface;
1413
};
1514

1615
const _self: SharedWorkerGlobalScope = self as any;
@@ -20,7 +19,7 @@ const OPEN_DB_LOCK = 'open-wasqlite-db';
2019

2120
let nextClientId = 1;
2221

23-
const openDB = async (dbFileName: string): Promise<DBWorkerInterface> => {
22+
const openDB = async (dbFileName: string): Promise<DBFunctionsInterface> => {
2423
// Prevent multiple simultaneous opens from causing race conditions
2524
return navigator.locks.request(OPEN_DB_LOCK, async () => {
2625
const clientId = nextClientId++;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import * as Comlink from 'comlink';
2-
import { _openDB } from './open-db';
2+
import { _openDB } from '../../shared/open-db';
33

44
Comlink.expose(async (dbFileName: string) => Comlink.proxy(await _openDB(dbFileName)));

packages/web/src/worker/db/open-worker-database.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Comlink from 'comlink';
2-
import { OpenDB } from './open-db';
2+
import type { OpenDB } from '../../shared/types';
33

44
/**
55
* Opens a shared or dedicated worker which exposes opening of database connections

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PowerSyncCredentials, SyncStatusOptions } from '@powersync/common';
1+
import type { PowerSyncCredentials, SyncStatusOptions } from '@powersync/common';
22

33
/**
44
* The client side port should provide these methods.

0 commit comments

Comments
 (0)