Skip to content

fix(wa-sqlite): recover failed OPFS initialization - #230

Merged
Antonov548 merged 8 commits into
mainfrom
fix/wa-sqlite-opfs-recovery-main
Aug 11, 2026
Merged

fix(wa-sqlite): recover failed OPFS initialization#230
Antonov548 merged 8 commits into
mainfrom
fix/wa-sqlite-opfs-recovery-main

Conversation

@marcus-pousette

@marcus-pousette marcus-pousette commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Before

fallback: "throw" was not forwarded into dedicated or SharedWorker initialization. If OPFS startup failed, the worker attempted memory initialization before the outer client rejected. Partial SQLite/VFS state and SharedWorker ports could remain, making later initialization unreliable.

After

Workers now follow the same policy as direct clients:

  • throw: clean partial resources and reject without opening memory storage.
  • memory: when the OPFS VFS or database cannot open, clean partial resources and retry using a fresh wa-sqlite module.

TreeCRDT extension or document initialization failures still reject instead of silently replacing a persistent database with empty memory storage. SharedWorker initialization, close, and drop release both client- and worker-side port ownership. Successful OPFS startup is unchanged.

Impact on em

em uses OPFS with fallback: "throw" in a dedicated worker. Storage startup failures will now reject cleanly and predictably, allowing em to handle the unsupported-storage state explicitly. This does not add OPFS support where the browser disables it, such as Safari Private Browsing, and em still owns the user-facing initialization error state.

This is the main-based replacement for the OPFS recovery work from #208 and leaves the teardown behavior released through #229 unchanged.

@marcus-pousette
marcus-pousette marked this pull request as draft August 5, 2026 16:05
@marcus-pousette
marcus-pousette marked this pull request as ready for review August 5, 2026 16:11
@marcus-pousette
marcus-pousette force-pushed the fix/wa-sqlite-opfs-recovery-main branch from e6f7a42 to 2e31d27 Compare August 10, 2026 20:31

type LifecycleHarness = NonNullable<Window['__treecrdtLifecycle']>;
type LifecycleRuntime = 'direct' | 'dedicated-worker' | 'shared-worker';
type LifecycleOptions = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a duplicate of the same type from packages/treecrdt-wa-sqlite/e2e/src/lifecycle.ts? Can it be reused?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Goood catch fixed!

fallback?: 'memory' | 'throw';
filename: string;
runtime: LifecycleRuntime;
sharedWorkerName?: string;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it necessary to provide the name of the shared worker?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit name is needed for these regression tests, where want it to be the same name to make sure stale port/session cleanup works. I added a comment


const OPFS_VFS_NAME = 'opfs';

function errorMessage(err: unknown): string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have so many such helper functions that create bloat in the code. Maybe let's try to remove them one by one

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, fixed! See my latest commit

const handle = await sqlite3.open_v2(filename, undefined, OPFS_VFS_NAME);
return { handle, vfs: initializedVfs };
} catch (err) {
await closeIgnoringErrors(vfs?.close ? () => vfs!.close!() : undefined);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also looks scary. Can we simplify it. Also, closeIgnoringErrors seems as another "helper" function. I would like to simplify such conditions vfs?.close ? () => vfs!.close!()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.. I removed closeIgnoringErrors and the callback expressions. Cleanup is now performed directly with try/catch. The catch remains so a cleanup failure cannot replace the original initialization/open error. This keeps the same behavior with simpler code

@Antonov548
Antonov548 merged commit e44e182 into main Aug 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants