Skip to content

Commit 32e342a

Browse files
[Fix] SQL options (#252)
1 parent 02ae5de commit 32e342a

File tree

7 files changed

+20890
-15519
lines changed

7 files changed

+20890
-15519
lines changed

.changeset/few-hounds-return.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@powersync/common': patch
3+
'@powersync/web': patch
4+
---
5+
6+
Fix: correctly apply SQLOpen flags. This fixes an issue where `PowerSyncDatabase` constructor `flags` options were not used when opening SQLite connections in web.

demos/example-electron/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
},
2020
"license": "MIT",
2121
"dependencies": {
22+
"@emotion/react": "^11.13.0",
23+
"@emotion/styled": "^11.13.0",
2224
"@journeyapps/wa-sqlite": "~0.2.0",
2325
"@mui/icons-material": "^5.15.16",
2426
"@mui/material": "^5.15.16",

packages/common/src/client/AbstractPowerSyncDatabase.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import { SyncStatus } from '../db/crud/SyncStatus';
1414
import { UploadQueueStats } from '../db/crud/UploadQueueStatus';
1515
import { Schema } from '../db/schema/Schema';
1616
import { BaseObserver } from '../utils/BaseObserver';
17+
import { ControlledExecutor } from '../utils/ControlledExecutor';
1718
import { mutexRunExclusive } from '../utils/mutex';
1819
import { quoteIdentifier } from '../utils/strings';
20+
import { SQLOpenFactory, SQLOpenOptions, isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory';
1921
import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector';
2022
import { BucketStorageAdapter, PSInternalTable } from './sync/bucket/BucketStorageAdapter';
2123
import { CrudBatch } from './sync/bucket/CrudBatch';
@@ -24,12 +26,10 @@ import { CrudTransaction } from './sync/bucket/CrudTransaction';
2426
import {
2527
AbstractStreamingSyncImplementation,
2628
DEFAULT_CRUD_UPLOAD_THROTTLE_MS,
27-
StreamingSyncImplementationListener,
29+
PowerSyncConnectionOptions,
2830
StreamingSyncImplementation,
29-
PowerSyncConnectionOptions
31+
StreamingSyncImplementationListener
3032
} from './sync/stream/AbstractStreamingSyncImplementation';
31-
import { ControlledExecutor } from '../utils/ControlledExecutor';
32-
import { SQLOpenFactory, SQLOpenOptions, isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory';
3333

3434
export interface DisconnectAndClearOptions {
3535
/** When set to false, data in local-only tables is preserved. */
@@ -139,6 +139,14 @@ export const DEFAULT_POWERSYNC_DB_OPTIONS = {
139139
*/
140140
export const DEFAULT_LOCK_TIMEOUT_MS = 120_000; // 2 mins
141141

142+
/**
143+
* Tests if the input is a {@link PowerSyncDatabaseOptionsWithSettings}
144+
* @internal
145+
*/
146+
export const isPowerSyncDatabaseOptionsWithSettings = (test: any): test is PowerSyncDatabaseOptionsWithSettings => {
147+
return typeof test == 'object' && isSQLOpenOptions(test.database);
148+
};
149+
142150
export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDBListener> {
143151
/**
144152
* Transactions should be queued in the DBAdapter, but we also want to prevent
@@ -182,8 +190,8 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
182190
this._database = database;
183191
} else if (isSQLOpenFactory(database)) {
184192
this._database = database.openDB();
185-
} else if (isSQLOpenOptions(database)) {
186-
this._database = this.openDBAdapter(database);
193+
} else if (isPowerSyncDatabaseOptionsWithSettings(options)) {
194+
this._database = this.openDBAdapter(options);
187195
}
188196

189197
this.bucketStorageAdapter = this.generateBucketStorageAdapter();
@@ -223,7 +231,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
223231
/**
224232
* Opens the DBAdapter given open options using a default open factory
225233
*/
226-
protected abstract openDBAdapter(options: SQLOpenOptions): DBAdapter;
234+
protected abstract openDBAdapter(options: PowerSyncDatabaseOptionsWithSettings): DBAdapter;
227235

228236
protected abstract generateSyncStreamImplementation(
229237
connector: PowerSyncBackendConnector

packages/react-native/src/db/PowerSyncDatabase.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {
22
AbstractPowerSyncDatabase,
33
AbstractStreamingSyncImplementation,
4-
PowerSyncBackendConnector,
5-
SqliteBucketStorage,
64
BucketStorageAdapter,
75
DBAdapter,
8-
SQLOpenOptions
6+
PowerSyncBackendConnector,
7+
PowerSyncDatabaseOptionsWithSettings,
8+
SqliteBucketStorage
99
} from '@powersync/common';
1010
import { ReactNativeRemote } from '../sync/stream/ReactNativeRemote';
1111
import { ReactNativeStreamingSyncImplementation } from '../sync/stream/ReactNativeStreamingSyncImplementation';
@@ -32,8 +32,8 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
3232
* Opens a DBAdapter using React Native Quick SQLite as the
3333
* default SQLite open factory.
3434
*/
35-
protected openDBAdapter(options: SQLOpenOptions): DBAdapter {
36-
const defaultFactory = new ReactNativeQuickSqliteOpenFactory(options);
35+
protected openDBAdapter(options: PowerSyncDatabaseOptionsWithSettings): DBAdapter {
36+
const defaultFactory = new ReactNativeQuickSqliteOpenFactory(options.database);
3737
return defaultFactory.openDB();
3838
}
3939

packages/web/src/db/PowerSyncDatabase.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
import {
22
type AbstractStreamingSyncImplementation,
3-
type PowerSyncBackendConnector,
43
type BucketStorageAdapter,
4+
type PowerSyncBackendConnector,
55
type PowerSyncCloseOptions,
66
type PowerSyncConnectionOptions,
77
AbstractPowerSyncDatabase,
8-
SqliteBucketStorage,
9-
DEFAULT_POWERSYNC_CLOSE_OPTIONS,
108
DBAdapter,
11-
SQLOpenOptions,
9+
DEFAULT_POWERSYNC_CLOSE_OPTIONS,
10+
PowerSyncDatabaseOptions,
1211
PowerSyncDatabaseOptionsWithDBAdapter,
1312
PowerSyncDatabaseOptionsWithOpenFactory,
1413
PowerSyncDatabaseOptionsWithSettings,
15-
PowerSyncDatabaseOptions
14+
SqliteBucketStorage
1615
} from '@powersync/common';
1716
import { Mutex } from 'async-mutex';
18-
import { WebRemote } from './sync/WebRemote';
17+
import { WASQLiteOpenFactory } from './adapters/wa-sqlite/WASQLiteOpenFactory';
18+
import { DEFAULT_WEB_SQL_FLAGS, resolveWebSQLFlags, WebSQLFlags } from './adapters/web-sql-flags';
1919
import { SharedWebStreamingSyncImplementation } from './sync/SharedWebStreamingSyncImplementation';
2020
import { SSRStreamingSyncImplementation } from './sync/SSRWebStreamingSyncImplementation';
21+
import { WebRemote } from './sync/WebRemote';
2122
import {
2223
WebStreamingSyncImplementation,
2324
WebStreamingSyncImplementationOptions
2425
} from './sync/WebStreamingSyncImplementation';
25-
import { WASQLiteOpenFactory } from './adapters/wa-sqlite/WASQLiteOpenFactory';
26-
import { DEFAULT_WEB_SQL_FLAGS, resolveWebSQLFlags, WebSQLFlags } from './adapters/web-sql-flags';
2726

2827
export interface WebPowerSyncFlags extends WebSQLFlags {
2928
/**
@@ -92,8 +91,11 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
9291

9392
async _initialize(): Promise<void> {}
9493

95-
protected openDBAdapter(options: SQLOpenOptions): DBAdapter {
96-
const defaultFactory = new WASQLiteOpenFactory({ ...options, flags: this.resolvedFlags });
94+
protected openDBAdapter(options: WebPowerSyncDatabaseOptionsWithSettings): DBAdapter {
95+
const defaultFactory = new WASQLiteOpenFactory({
96+
...options.database,
97+
flags: resolveWebPowerSyncFlags(options.flags)
98+
});
9799
return defaultFactory.openDB();
98100
}
99101

packages/web/tests/open.test.ts

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { describe, expect, it } from 'vitest';
21
import { AbstractPowerSyncDatabase } from '@powersync/common';
32
import {
43
PowerSyncDatabase,
54
WASQLiteDBAdapter,
6-
WASQLitePowerSyncDatabaseOpenFactory,
7-
WASQLiteOpenFactory
5+
WASQLiteOpenFactory,
6+
WASQLitePowerSyncDatabaseOpenFactory
87
} from '@powersync/web';
8+
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
99
import { testSchema } from './utils/testDb';
1010

1111
const testId = '2290de4f-0488-4e50-abed-f8e8eb1d0b42';
@@ -18,6 +18,45 @@ export const basicTest = async (db: AbstractPowerSyncDatabase) => {
1818
};
1919

2020
describe('Open Methods', () => {
21+
let originalSharedWorker: typeof SharedWorker;
22+
let originalWorker: typeof Worker;
23+
24+
const sharedWorkerProxyHandler = {
25+
construct(target: typeof SharedWorker, args: any[]) {
26+
const [url, options] = args;
27+
28+
// Call the original constructor
29+
const instance = new target(url, options);
30+
return instance;
31+
}
32+
};
33+
const workerProxyHandler = {
34+
construct(target: typeof Worker, args: any[]) {
35+
const [url, options] = args;
36+
37+
// Call the original constructor
38+
const instance = new target(url, options);
39+
return instance;
40+
}
41+
};
42+
43+
beforeAll(() => {
44+
// Store the original SharedWorker constructor
45+
originalSharedWorker = SharedWorker;
46+
originalWorker = Worker;
47+
48+
// Create a proxy to intercept the worker constructors
49+
// The vi.SpyOn does not work well with constructors
50+
window.SharedWorker = new Proxy(SharedWorker, sharedWorkerProxyHandler);
51+
window.Worker = new Proxy(Worker, workerProxyHandler);
52+
});
53+
54+
afterAll(() => {
55+
// Restore Worker
56+
window.SharedWorker = originalSharedWorker;
57+
window.Worker = originalWorker;
58+
});
59+
2160
it('Should open PowerSync clients from old factory methods', async () => {
2261
const db = new WASQLitePowerSyncDatabaseOpenFactory({
2362
dbFilename: `test-legacy.db`,
@@ -46,4 +85,46 @@ describe('Open Methods', () => {
4685

4786
await basicTest(db);
4887
});
88+
89+
it('Should use shared worker for multiple tabs', async () => {
90+
const sharedSpy = vi.spyOn(sharedWorkerProxyHandler, 'construct');
91+
92+
const db = new PowerSyncDatabase({ database: { dbFilename: 'options-test.db' }, schema: testSchema });
93+
94+
await basicTest(db);
95+
96+
expect(sharedSpy).toBeCalledTimes(1);
97+
});
98+
99+
it('Should use dedicated worker when multiple tabs disabled', async () => {
100+
const sharedSpy = vi.spyOn(sharedWorkerProxyHandler, 'construct');
101+
const dedicatedSpy = vi.spyOn(workerProxyHandler, 'construct');
102+
103+
const db = new PowerSyncDatabase({
104+
database: { dbFilename: 'options-test.db' },
105+
schema: testSchema,
106+
flags: { enableMultiTabs: false }
107+
});
108+
109+
await basicTest(db);
110+
111+
expect(sharedSpy).toBeCalledTimes(0);
112+
expect(dedicatedSpy).toBeCalledTimes(1);
113+
});
114+
115+
it('Should not use workers when specified', async () => {
116+
const sharedSpy = vi.spyOn(sharedWorkerProxyHandler, 'construct');
117+
const dedicatedSpy = vi.spyOn(workerProxyHandler, 'construct');
118+
119+
const db = new PowerSyncDatabase({
120+
database: { dbFilename: 'options-test.db' },
121+
schema: testSchema,
122+
flags: { useWebWorker: false }
123+
});
124+
125+
await basicTest(db);
126+
127+
expect(sharedSpy).toBeCalledTimes(0);
128+
expect(dedicatedSpy).toBeCalledTimes(0);
129+
});
49130
});

0 commit comments

Comments
 (0)