Skip to content

Commit 152f400

Browse files
Merge remote-tracking branch 'origin/main' into fix/opfs-multitab-issue
2 parents e5253c9 + d0c67b1 commit 152f400

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed
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+
Avoid binding `this` when disposing table change listeners in the web adapter to prevent Comlink serialization errors on close.

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,14 @@ export class LockedAsyncDatabaseAdapter
234234
*/
235235
async close() {
236236
this.closing = true;
237-
this._disposeTableChangeListener?.();
237+
/**
238+
* Note that we obtain a reference to the callback to avoid calling the callback with `this` as the context.
239+
* This is to avoid Comlink attempting to clone `this` when calling the method.
240+
*/
241+
const dispose = this._disposeTableChangeListener;
242+
if (dispose) {
243+
dispose();
244+
}
238245
this.pendingAbortControllers.forEach((controller) => controller.abort('Closed'));
239246
await this.baseDB?.close?.();
240247
this.closed = true;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { LockedAsyncDatabaseAdapter } from '../src/db/adapters/LockedAsyncDatabaseAdapter';
3+
import { AsyncDatabaseConnection } from '../src/db/adapters/AsyncDatabaseConnection';
4+
import { DEFAULT_CACHE_SIZE_KB, TemporaryStorageOption } from '../src/db/adapters/web-sql-flags';
5+
import { ResolvedWASQLiteOpenFactoryOptions } from '../src/db/adapters/wa-sqlite/WASQLiteOpenFactory';
6+
import { WASQLiteVFS } from '../src/db/adapters/wa-sqlite/WASQLiteConnection';
7+
8+
describe('LockedAsyncDatabaseAdapter.close', () => {
9+
it('calls the table change disposer without binding this', async () => {
10+
let thisValue: unknown;
11+
let called = false;
12+
13+
const disposer = function (this: unknown) {
14+
thisValue = this;
15+
called = true;
16+
};
17+
18+
const config: ResolvedWASQLiteOpenFactoryOptions = {
19+
dbFilename: 'test.db',
20+
flags: {
21+
broadcastLogs: true,
22+
disableSSRWarning: false,
23+
enableMultiTabs: false,
24+
useWebWorker: false,
25+
ssrMode: false
26+
},
27+
temporaryStorage: TemporaryStorageOption.MEMORY,
28+
cacheSizeKb: DEFAULT_CACHE_SIZE_KB,
29+
vfs: WASQLiteVFS.OPFSCoopSyncVFS
30+
};
31+
32+
const db: AsyncDatabaseConnection = {
33+
init: async () => {},
34+
close: async () => {},
35+
markHold: async () => 'hold',
36+
releaseHold: async () => {},
37+
isAutoCommit: async () => true,
38+
execute: async () => ({ rows: { _array: [], length: 0 } } as any),
39+
executeRaw: async () => [],
40+
executeBatch: async () => ({ rows: { _array: [], length: 0 } } as any),
41+
registerOnTableChange: async () => disposer,
42+
getConfig: async () => config
43+
};
44+
45+
const adapter = new LockedAsyncDatabaseAdapter({
46+
name: 'test-close',
47+
openConnection: async () => db
48+
});
49+
50+
await adapter.init();
51+
await adapter.close();
52+
53+
expect(called).toBe(true);
54+
expect(thisValue).toBeUndefined();
55+
});
56+
});

0 commit comments

Comments
 (0)