Skip to content

Commit 4e30592

Browse files
fix(wa-sqlite): restore client close and drop semantics
1 parent dbea906 commit 4e30592

1 file changed

Lines changed: 54 additions & 8 deletions

File tree

packages/treecrdt-wa-sqlite/src/client.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { detectOpfsSupport } from './opfs.js';
1+
import { clearOpfsStorage, detectOpfsSupport } from './opfs.js';
22
import type { Operation, ReplicaId } from '@treecrdt/interface';
33
import {
44
createTreecrdtSqliteWriter,
@@ -60,6 +60,7 @@ export type TreecrdtClient = TreecrdtEngine & {
6060
meta: TreecrdtMetaApi;
6161
local: TreecrdtLocalApi;
6262
close: () => Promise<void>;
63+
drop: () => Promise<void>;
6364
};
6465

6566
export type ClientOptions = {
@@ -141,8 +142,12 @@ async function createWorkerClient(opts: {
141142
{ resolve: (value: any) => void; reject: (err: Error) => void }
142143
>();
143144
let terminalError: Error | null = null;
145+
let closed = false;
146+
147+
const closedError = new Error(CLIENT_CLOSED_ERROR);
144148

145149
const call = <M extends RpcMethod>(method: M, params: RpcParams<M>): Promise<RpcResult<M>> => {
150+
if (closed) return Promise.reject(closedError);
146151
const id = nextId++;
147152
if (terminalError) return Promise.reject(terminalError);
148153
return new Promise((resolve, reject) => {
@@ -175,27 +180,44 @@ async function createWorkerClient(opts: {
175180
opts.docId,
176181
])) as { storage?: StorageMode; opfsError?: string } | undefined;
177182
const effectiveStorage: StorageMode = initResult?.storage === 'opfs' ? 'opfs' : 'memory';
183+
const cleanup = () => {
184+
closed = true;
185+
for (const { reject } of pending.values()) reject(closedError);
186+
pending.clear();
187+
worker.removeEventListener('error', onError);
188+
worker.removeEventListener('message', onMessage);
189+
worker.terminate();
190+
};
191+
178192
if (opts.requireOpfs && effectiveStorage !== 'opfs') {
179193
const reason = initResult?.opfsError ? `: ${initResult.opfsError}` : '';
180194
try {
181195
if (!terminalError) await call('close', [] as RpcParams<'close'>);
182196
} catch {
183197
// ignore close errors on init failure
184198
} finally {
185-
worker.removeEventListener('error', onError);
186-
worker.removeEventListener('message', onMessage);
187-
worker.terminate();
199+
cleanup();
188200
}
189201
throw new Error(`OPFS requested but could not be initialized${reason}`);
190202
}
191203

192204
const closeImpl = async () => {
205+
if (closed) return;
193206
try {
194207
if (!terminalError) await call('close', [] as RpcParams<'close'>);
208+
cleanup();
209+
} finally {
210+
// noop: cleanup already handles terminal teardown, and repeated close is idempotent
211+
}
212+
};
213+
214+
const dropImpl = async () => {
215+
if (closed) return;
216+
try {
217+
if (!terminalError) await call('drop', [] as RpcParams<'drop'>);
218+
cleanup();
195219
} finally {
196-
worker.removeEventListener('error', onError);
197-
worker.removeEventListener('message', onMessage);
198-
worker.terminate();
220+
// noop: cleanup already handles terminal teardown, and repeated drop is idempotent
199221
}
200222
};
201223

@@ -205,6 +227,7 @@ async function createWorkerClient(opts: {
205227
docId: opts.docId,
206228
call,
207229
close: closeImpl,
230+
drop: dropImpl,
208231
});
209232
}
210233

@@ -253,8 +276,11 @@ async function createDirectClient(opts: {
253276
message: err instanceof Error ? err.message : String(err),
254277
}),
255278
);
279+
let closed = false;
280+
const closedError = new Error(CLIENT_CLOSED_ERROR);
256281

257282
const call: RpcCall = async (method, params) => {
283+
if (closed) throw closedError;
258284
try {
259285
switch (method) {
260286
case 'sqlExec': {
@@ -357,9 +383,17 @@ async function createDirectClient(opts: {
357383
const [replica, node, payload] = params as RpcParams<'localPayload'>;
358384
return (await localWriterFor(Uint8Array.from(replica)).payload(node, payload)) as any;
359385
}
360-
case 'close':
386+
case 'close': {
387+
if (db.close) await db.close();
388+
return undefined as any;
389+
}
390+
case 'drop': {
361391
if (db.close) await db.close();
392+
if (finalStorage === 'opfs') {
393+
await clearOpfsStorage(filename);
394+
}
362395
return undefined as any;
396+
}
363397
default:
364398
throw new Error(`unsupported direct method: ${method}`);
365399
}
@@ -374,7 +408,17 @@ async function createDirectClient(opts: {
374408
docId: opts.docId,
375409
call,
376410
close: async () => {
411+
if (closed) return;
412+
if (db.close) await db.close();
413+
closed = true;
414+
},
415+
drop: async () => {
416+
if (closed) return;
377417
if (db.close) await db.close();
418+
if (finalStorage === 'opfs') {
419+
await clearOpfsStorage(filename);
420+
}
421+
closed = true;
378422
},
379423
});
380424
}
@@ -387,6 +431,7 @@ function makeTreecrdtClientFromCall(opts: {
387431
docId: string;
388432
call: RpcCall;
389433
close: () => Promise<void>;
434+
drop: () => Promise<void>;
390435
}): TreecrdtClient {
391436
const call = opts.call;
392437
let closePromise: Promise<void> | null = null;
@@ -510,6 +555,7 @@ function makeTreecrdtClientFromCall(opts: {
510555
payload: localPayloadImpl,
511556
},
512557
close: closeImpl,
558+
drop: opts.drop,
513559
};
514560
}
515561

0 commit comments

Comments
 (0)