Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enabling OPFS for client-wasm #300

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

callum-gander
Copy link

This is not a finished PR, it's to start a discussion so I can better understand how codeowners would want this work done, if at all

What and why I've done this

  • Currently, LibSQL only runs in memory or remotely for browser extensions. This isn't ideal for developers if they want to provide a persistent, free option for users before subscribing that would then move to a paid, remote Turso database
  • SQLite has the ability to run in web workers and be persisted on OPFS. This should also be possible for LibSQL (excluding Turso features)
  • As a browser extension developer that wants to build a tool with free and paid options and doesn't want to pay for free users DB cost on Turso, I need LibSQL to work in both OPFS and remotely
  • To this end, I fiddled around and got OPFS working as a PoC but there are substantial issues with this approach and I want to get feedback so that I or someone else can build on this work to get something ready to merge into the main package to enable this functionality. Find the example repo here: https://github.com/callum-gander/libsql-opfs

How this works

  • Browser extension page starts web worker
  • Web worker initialises and sends message to browser extension page when ready
  • Once web worker is ready, browser extension page sends a message telling the web worker to initialise the db as well as using browser.runtime.getURL('assets/sqlite3.wasm) to get the location of the sqlite WASM
  • Web worker then calls the initSqlite3 function provided by the custom client with the sqlite WASM file's path
  • This custom initSqlite3 function is within the context of the client and passes the path as the locateFile arg into the sqlite3InitModule that already exists within the original client
  • We then use createClient as we would normally. However, under the hood, we've changed two lines within the custom client, basically using this sqlite3.oo1.OpfsDb instead of this sqlite3.oo1.DB
  • The client will then be fully initialised, working and can be tested

Problems and reasons for this approach

  • Almost all the issues with this are due to the fairly painful restrictions that browsers, specifically Chrome since MV3, puts on browser extensions
  • No buildtime URLs for assets
    • one of the core problems is that in a browser extension environment to run a wasm file, particularly in environments like web workers, we need to have it as a web accessible resource, in our case placed in the assets/sqlite3.wasm path (yes, it's possible to bundle the WASM file as a string, even if it is very inefficient, but I believe I ended up with CSP restrictions set by the browser that cannot be overridden)
    • This is problematic, as @libsql/client-wasm needs the declared and initialised sqlite3 WASM file within the wasm.js file that declares the client and then directly references sqlite3 and uses it after it's been initialised
    • sqlite3InitModule is an emscripten module and thus in theory, it would be pretty simple to just patch the module with locateFile in it's params along with the url to import the location of the wasm file (even though this is not directly within the wasm client and is in the libsql-wasm-experimental dependencies files)
    • But this URL is unknowable until runtime due to the limitations of browser extensions and must be gotten with browser.runtime.getURL
    • Ultimately, because of this, the approach I landed on that worked was having to use a modified client that didn't initialise sqlite until explicitly called and allowed us to pass the sqlite location as a param
    • This might not be ideal but it does work
  • Browser restrictions around web workers spun up from node modules
    • I might have missed something here but essentially the browser wouldn't run the web workers called from the sqlite emscripten module, specifically the opfs-proxy one, no matter what I tried
    • I ended up just copying the whole libsql-wasm-experimental module directly into my code, as this means the web workers are technically my bundled code and the browser will let them run without issue when the module, that's also now within my code, tries to call them
    • Again, this isn't optimal, but it feels like, unless someone else can think of a promising alternative approach, the majority of the issues lie with the browser itself, so copying the npm module as a lib folder into your own code might be the best workaround here
  • No parameters to initialise opfsDb rather than just DB
    • This is probably the easiest one to change within the client itself. Essentially, currently, it's just hardcoded to DB
    • As far as I can see, this would just mean adding an additional param to the config or piggy backing off another param if set to a specific value so that when that condition is met, switch from DB to opfsDb
    • I don't see why this isn't provided, as libsql-wasm-experimental shows, this is just regular sqlite-wasm , OPFS works like a charm, the only issue I could really see was that obviously none of the turso functionality was actually available, e.g. sync being disabled
    • Is there a technical reason why sync is disabled? Are there any reasons why these features would be incompatible with OPFS?
  • OPFS, like all browser storage, isn't truly persistent or reliable
    • This is the biggest issue with OPFS. Despite apparently being persistent, it can be wiped by the browser without notice
    • I'm genuinely not sure how this could be solved. Obviously, the browsers' aren't going to adjust this, so my only guess here would be detecting when this has happened and then pulling the DB again from Turso
    • The real worry is that the DB gets deleted from OPFS which then might somehow get synced with Turso, wiping the remote DB. However, I'm not sure how realistic this concern is, I don't know the internals of how LibSQL and Turso works at a deep level

Why this is important for the broader LibSQL project

  • Currently, all the SQLite variants open to browser extension developers have a host of problems, from no sync or backup, to only running in memory, and so on
  • This feels like it should be relatively low lift to get OPFS running in LibSQL and when you do, it feels like this would be rapidly adopted
  • Of course, this is overall a small market, but it feels like one which, again, should be relatively doable and which no one else seems to be reaching for
  • I did this PR specifically because I really want to use LibSQL and Turso for a project I'm trying to turn into a business and intend for the primary user DB to be LibSQL with Turso

Next Steps

  • This is a PoC, not anything ready to merge, but I really wanted to hopefully kick off a conversation about this and get feedback from the core team on what would need to be done to bring this functionality into the main package given the limitations I've outlined
  • From these, you can see that there are three main requirements to get OPFS working in browser extensions:
    • Ability to pass the WASM url and initialise the WASM module at runtime, not by the package itself
    • Ability to run the related web workers without issue
    • Ability to specify that we use OPFS
  • I would also argue that the ability to allow users to choose where to load their WASM file from and initialise it independently of the module itself would be useful for developers in environments beyond just browser extensions

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.

1 participant