Skip to content

Commit 44582ef

Browse files
Opfs uploads (#505)
Co-authored-by: Christiaan Landman <[email protected]>
1 parent 2770df4 commit 44582ef

File tree

6 files changed

+69
-22
lines changed

6 files changed

+69
-22
lines changed

.changeset/forty-moose-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/web': patch
3+
---
4+
5+
Fixed bug where using OPFS and reconnecting would cause upload triggers to fail.

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
jobs:
88
test:
99
name: Test Packages
10-
runs-on: ubuntu-latest
10+
runs-on: ubuntu-xl
1111
steps:
1212
- uses: actions/checkout@v4
1313
with:

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

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ export class SharedSyncImplementation
180180
await this.waitForReady();
181181
// This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
182182
return getNavigatorLocks().request('shared-sync-connect', async () => {
183+
if (!this.dbAdapter) {
184+
await this.openInternalDB();
185+
}
183186
this.syncStreamClient = this.generateStreamingImplementation();
184187
this.lastConnectOptions = options;
185188
this.syncStreamClient.registerListener({
@@ -231,12 +234,7 @@ export class SharedSyncImplementation
231234
}
232235

233236
const trackedPort = this.ports[index];
234-
if (trackedPort.db) {
235-
trackedPort.db.close();
236-
}
237-
238-
// Release proxy
239-
trackedPort.clientProvider[Comlink.releaseProxy]();
237+
// Remove from the list of active ports
240238
this.ports.splice(index, 1);
241239

242240
/**
@@ -249,12 +247,25 @@ export class SharedSyncImplementation
249247
}
250248
});
251249

252-
if (this.dbAdapter == trackedPort.db && this.syncStreamClient) {
253-
await this.disconnect();
254-
// Ask for a new DB worker port handler
255-
await this.openInternalDB();
256-
await this.connect(this.lastConnectOptions);
250+
const shouldReconnect = !!this.syncStreamClient;
251+
if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
252+
if (shouldReconnect) {
253+
await this.disconnect();
254+
}
255+
256+
// Clearing the adapter will result in a new one being opened in connect
257+
this.dbAdapter = null;
258+
259+
if (shouldReconnect) {
260+
await this.connect(this.lastConnectOptions);
261+
}
257262
}
263+
264+
if (trackedPort.db) {
265+
trackedPort.db.close();
266+
}
267+
// Release proxy
268+
trackedPort.clientProvider[Comlink.releaseProxy]();
258269
}
259270

260271
triggerCrudUpload() {
@@ -334,6 +345,10 @@ export class SharedSyncImplementation
334345

335346
protected async openInternalDB() {
336347
const lastClient = this.ports[this.ports.length - 1];
348+
if (!lastClient) {
349+
// Should not really happen in practice
350+
throw new Error(`Could not open DB connection since no client is connected.`);
351+
}
337352
const workerPort = await lastClient.clientProvider.getDBWorkerPort();
338353
const remote = Comlink.wrap<OpenAsyncDatabaseConnection>(workerPort);
339354
const identifier = this.syncParams!.dbParams.dbFilename;

packages/web/tests/main.test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
21
import { AbstractPowerSyncDatabase } from '@powersync/common';
2+
import { PowerSyncDatabase, WASQLiteOpenFactory, WASQLiteVFS } from '@powersync/web';
33
import { v4 as uuid } from 'uuid';
4-
import { TestDatabase, generateTestDb } from './utils/testDb';
4+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5+
import { TestDatabase, generateTestDb, testSchema } from './utils/testDb';
56
// TODO import tests from a common package
67

78
describe('Basic', () => {
89
let dbWithoutWebWorker: AbstractPowerSyncDatabase;
910
let dbWithWebWorker: AbstractPowerSyncDatabase;
11+
let dbWithOPFS: AbstractPowerSyncDatabase;
1012

1113
beforeEach(() => {
1214
dbWithoutWebWorker = generateTestDb({ useWebWorker: false });
1315
dbWithWebWorker = generateTestDb();
16+
dbWithOPFS = new PowerSyncDatabase({
17+
database: new WASQLiteOpenFactory({ dbFilename: 'basic.sqlite', vfs: WASQLiteVFS.OPFSCoopSyncVFS }),
18+
schema: testSchema
19+
});
1420
});
1521

1622
/**
@@ -19,13 +25,16 @@ describe('Basic', () => {
1925
const itWithDBs = (name: string, test: (db: AbstractPowerSyncDatabase) => Promise<void>) => {
2026
it(`${name} - with web worker`, () => test(dbWithWebWorker));
2127
it(`${name} - without web worker`, () => test(dbWithoutWebWorker));
28+
it(`${name} - with OPFS`, () => test(dbWithOPFS));
2229
};
2330

2431
afterEach(async () => {
2532
await dbWithWebWorker.disconnectAndClear();
2633
await dbWithWebWorker.close();
2734
await dbWithoutWebWorker.disconnectAndClear();
2835
await dbWithoutWebWorker.close();
36+
await dbWithOPFS.disconnectAndClear();
37+
await dbWithOPFS.close();
2938
});
3039

3140
describe('executeQuery', () => {

packages/web/tests/stream.test.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { WASQLiteOpenFactory, WASQLiteVFS } from '@powersync/web';
12
import Logger from 'js-logger';
23
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
34
import { TestConnector } from './utils/MockStreamOpenFactory';
@@ -18,19 +19,36 @@ describe(
1819
name: string,
1920
test: (createConnectedDatabase: () => ReturnType<typeof generateConnectedDatabase>) => Promise<void>
2021
) => {
21-
const funcWithWebWorker = generateConnectedDatabase;
22+
const funcWithWebWorker = () =>
23+
generateConnectedDatabase({
24+
powerSyncOptions: {
25+
dbFilename: `test-stream-connection-worker.db`
26+
}
27+
});
2228
const funcWithoutWebWorker = () =>
2329
generateConnectedDatabase({
2430
powerSyncOptions: {
25-
dbFilename: 'test-stream-connection-no-worker.db',
31+
dbFilename: `test-stream-connection-no-worker.db`,
2632
flags: {
2733
useWebWorker: false
2834
}
2935
}
3036
});
3137

32-
it(`${name} - with web worker`, () => test(funcWithWebWorker));
33-
it(`${name} - without web worker`, () => test(funcWithoutWebWorker));
38+
it.sequential(`${name} - with web worker`, () => test(funcWithWebWorker));
39+
it.sequential(`${name} - without web worker`, () => test(funcWithoutWebWorker));
40+
it.sequential(`${name} - with OPFS`, () =>
41+
test(() =>
42+
generateConnectedDatabase({
43+
powerSyncOptions: {
44+
database: new WASQLiteOpenFactory({
45+
dbFilename: `test-stream-connection-opfs.db`,
46+
vfs: WASQLiteVFS.OPFSCoopSyncVFS
47+
})
48+
}
49+
})
50+
)
51+
);
3452
};
3553

3654
beforeAll(() => Logger.useDefaults());
@@ -101,7 +119,7 @@ describe(
101119
const throwCounter = 2;
102120
uploadSpy.mockImplementation(async (db) => {
103121
if (uploadCounter++ < throwCounter) {
104-
throw new Error('No uploads yet');
122+
throw new Error(`No uploads yet`);
105123
}
106124
// Now actually do the upload
107125
const tx = await db.getNextCrudTransaction();

packages/web/vitest.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ const config: UserConfigExport = {
4646
instances: [
4747
{
4848
browser: 'chromium'
49-
},
50-
{
51-
browser: 'firefox'
5249
}
50+
// {
51+
// browser: 'firefox'
52+
// }
5353
// This requires some additional work to get all tests passing
5454
// {
5555
// browser: 'webkit'

0 commit comments

Comments
 (0)