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

Add support for Node.js #48

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

Add support for Node.js #48

wants to merge 11 commits into from

Conversation

tomayac
Copy link
Collaborator

@tomayac tomayac commented Oct 17, 2023

No description provided.

@tomayac
Copy link
Collaborator Author

tomayac commented Oct 17, 2023

@sgbeal I managed to get a transient database to work on Node. I had to patch one self to be globalThis and it ran, see the changes (in sqlite3-node.mjs specifically) for details.

$ node demo/node.js
# Loading and initializing SQLite3 module...
# Done initializing. Running demo...
# Running SQLite3 version 3.43.2
# Creating a table...
# Insert some data using exec()...
# Query data with exec()...
# [ 20 ]
# [ 21 ]
# [ 22 ]

How can I create a persisted database, though? I tried:

// Works, but not persisted:
const db = new sqlite3.oo1.DB('./local', 'cw');

// `sqlite3.oo1.OpfsDb` doesn't exist.
const db = new sqlite3.oo1.OpfsDb('./local', 'cw');

// No such `vfs: kvvs`, same for `file:session?vfs=kvvs`. 
const db = new sqlite3.oo1.DB('file:local?vfs=kvvs', 'cw');

// Returns `0`.
sqlite3.capi.sqlite3_vfs_find("opfs")

Is this supposed to work somehow?

@sgbeal
Copy link
Collaborator

sgbeal commented Oct 17, 2023

Bummer, i thought we had eliminated the last of the "self" references. i'll fix that when i'm back on the computer. OPFS can't work on node. i would expect dbs to be filesystem local, but what you're seeing above is the Emscripten i/o proxy, which is transient. Perhaps there's a way to tell Emscripten to use node's native i/o support?

Please excuse brevity - mobile device.

@sgbeal
Copy link
Collaborator

sgbeal commented Oct 17, 2023

It just now occurs to me that node's native i/o is probably async, not posix-style, in which case it could not work with sqlite without an async-to-sync proxy vfs like the first OPFS vfs.

@sgbeal
Copy link
Collaborator

sgbeal commented Oct 17, 2023

It turns out that that particular "self" reference is dead code and can be removed entirely:

Index: ext/wasm/api/sqlite3-api-worker1.js
==================================================================
--- ext/wasm/api/sqlite3-api-worker1.js
+++ ext/wasm/api/sqlite3-api-worker1.js
@@ -331,11 +331,10 @@
   'use strict';
   const toss = (...args)=>{throw new Error(args.join(' '))};
   if(!(globalThis.WorkerGlobalScope instanceof Function)){
     toss("initWorker1API() must be run from a Worker thread.");
   }
-  const self = this.self;
   const sqlite3 = this.sqlite3 || toss("Missing this.sqlite3 object.");
   const DB = sqlite3.oo1.DB;
 
   /**
      Returns the app-wide unique ID for the given db, creating one if
@@ -655,7 +654,7 @@
       //},
       result: result
     }, wState.xfer);
   };
   globalThis.postMessage({type:'sqlite3-api',result:'worker1-ready'});
-}.bind({self, sqlite3});
+}.bind({sqlite3});
 });

Upstream is being patched to remove that part and fix one more self ref which was just uncovered.

@tomayac
Copy link
Collaborator Author

tomayac commented Oct 18, 2023

Perhaps there's a way to tell Emscripten to use node's native i/o support?

@sgbeal There's the NODEFS that should work. Have you looked at this?

It uses node’s synchronous FS API to immediately persist any data written to the Emscripten file system to your local disk.

Source

@sgbeal
Copy link
Collaborator

sgbeal commented Oct 18, 2023

There's the NODEFS that should work. Have you looked at this?

i have not - i don't use node and don't currently have the bandwidth to go down that rabbit hole :/. Through mid-January my computing time is far more limited than usual because of familial responsibilities.

@sgbeal
Copy link
Collaborator

sgbeal commented Oct 18, 2023

But yes, i believe doing a separate build with that enabled would resolve the persistent storage in node, and have added that to my TODOs to try out.

@tomayac
Copy link
Collaborator Author

tomayac commented Oct 19, 2023

According to the docs, it can be activated via the -lnodefs.js flag.

@zoren
Copy link

zoren commented Oct 26, 2023

The addition of sqlite3InitModuleNode to index.mjs in itself would be helpful for creating transient databases in node.

@tomayac
Copy link
Collaborator Author

tomayac commented Oct 26, 2023

The addition of sqlite3InitModuleNode to index.mjs in itself would be helpful for creating transient databases in node.

I agree, but my fear is that people won't read the small print of "only transient databases supported" and expect full Node.js support. Hopefully we can actually make this happen soon.

@zoren
Copy link

zoren commented Nov 9, 2023

If I use this branch I can use sqlite3InitModuleNode fine from node, but I cannot use the package from vite as it complains about the node dependencies being loaded.

@DallasHoff
Copy link

Yeah, sqlite3InitModuleNode should probably be exported from a different entry point like @sqlite.org/sqlite-wasm/node.

@tomayac
Copy link
Collaborator Author

tomayac commented Nov 10, 2023

I have just created a separate entry point for Node: node.mjs. This should allow interested parties to play with transient databases in Node, and at the same time work in the browser.

@zoren
Copy link

zoren commented Nov 12, 2023

node needs to be before import for it to work for me:

    ".": {
      "types": "./index.d.ts",
      "node": "./node.mjs",
      "import": "./index.mjs",
      "main": "./index.mjs",
      "browser": "./index.mjs"
    },

But then it does work in both node and browser!

@sgbeal
Copy link
Collaborator

sgbeal commented Nov 12, 2023

i feel compelled to point out that relying on object property order is not a great idea, as whether or not the order is retained is dependent on the how the object is traversed. See:

https://stackoverflow.com/a/30919039/1458521

In this case, the order of traversal is an implementation detail of whatever reads that import block, and that block may or may not use an order-honoring traversal. Since it's read as JSON, and JSON (rightfully) leaves the ordering implementation-defined, that block can get re-ordered if it passes through arbitrary JSON implementations.

@zoren
Copy link

zoren commented Nov 12, 2023

i feel compelled to point out that relying on object property order is not a great idea, as whether or not the order is retained is dependent on the how the object is traversed. See:

I completely agree. However node does rely on the ordering of exports. For instance:

"default" This condition should always come last.

https://nodejs.org/api/packages.html#conditional-exports

@zoren
Copy link

zoren commented Nov 12, 2023

I made a small demo repo in case anyone is interested. It's using a fork of this branch where "node": "./node.mjs", is before "import": "./index.mjs", as mentioned above.

It runs demo-123 on main thread and in a worker side by side in the browser. In node they run one after the other.

https://github.com/zoren/sqlite-wasm-demo

move node before import in package.json
@tomayac
Copy link
Collaborator Author

tomayac commented Nov 13, 2023

I made a small demo repo in case anyone is interested. It's using a fork of this branch where "node": "./node.mjs", is before "import": "./index.mjs", as mentioned above.

It runs demo-123 on main thread and in a worker side by side in the browser. In node they run one after the other.

https://github.com/zoren/sqlite-wasm-demo

Thanks, merged your PR. This should allow you to use the current branch. We're slowly getting there. Transient seems solved now, pending is persistent databases.

@zoren
Copy link

zoren commented Nov 13, 2023

Adding:

export function sqlite3InitModuleNode(opts?: InitOptions): Promise<Sqlite3Static>;

to index.d.ts would reflect the interface as it is.

But maybe it would be better if node.mjs default exported a function like index.mjs does to keep the same interface?

@tomayac
Copy link
Collaborator Author

tomayac commented Nov 13, 2023

But maybe it would be better if node.mjs default exported a function like index.mjs does to keep the same interface?

Changed to this.

@matthewp
Copy link

matthewp commented Jan 8, 2024

This looks ready to go, anything that's being waited on?

@tomayac tomayac changed the title Node support test Add support for Node.js Jan 9, 2024
@tomayac
Copy link
Collaborator Author

tomayac commented Jan 9, 2024

This looks ready to go, anything that's being waited on?

It's ready to go if you don't need to persist your database, which makes me believe it's not ready for the majority of use cases.

penberg added a commit to tursodatabase/libsql-wasm-experimental that referenced this pull request Jan 15, 2024
@PabbleDabble
Copy link

Is there value to call out that this won't work in node in a persistent context in the readme?

Our app uses WebSql (only for local development) and I'm scrambling to find a replacement after reading https://developer.chrome.com/blog/sqlite-wasm-in-the-browser-backed-by-the-origin-private-file-system

I've spent a couple weeks now trying to get this or another option working, and just now got to this issue about node which seems to say that that this plugin doesn't support NODE + PERSTANCE

@sgbeal
Copy link
Collaborator

sgbeal commented Mar 6, 2024

Is there value to call out that this won't work in node in a persistent context in the readme?

Given that the only supported persistence options are browser-specific, calling out that they can't work in node seems superfluous.

I've spent a couple weeks now trying to get this or another option working, and just now got to this issue about node which seems to say that that this plugin doesn't support NODE + PERSTANCE

The documentation specifically says that browsers are the only current target: https://sqlite.org/wasm/doc/trunk/about.md.

@sgbeal
Copy link
Collaborator

sgbeal commented Mar 6, 2024

There's the NODEFS that should work. Have you looked at this?

i have not - i don't use node and don't currently have the bandwidth to go down that rabbit hole :/. Through mid-January my computing time is far more limited than usual because of familial responsibilities.

The main source tree now has a branch which modifies the node-specific build to use -lnodefs.js, which should enable NODEFS:

https://sqlite.org/src/timeline?r=wasm-nodefs&c=0bcbde7c

What is not clear is whether it will use that filesystem API by default, or whether it will still use the internal posix I/O wrappers for node. i can find no mention in the Emscripten docs about how to ensure that nodefs is made the default. It's possible that this only expose nodefs to JS, which wouldn't be helpful in our case because our module does not expose any Emscripten pieces to client-level code.

It is 100% untested as i am still, and hope to forever remain, a node-zero.

There are two options for trying this out:

First, from a checked-out copy of the main sqlite source tree:

$ fossil update wasm-nodefs
$ ./configure
$ cd ext/wasm
$ make
# go get coffee

the results will include jswasm/sqlite3-node.mjs, which uses the same jswasm/sqlite3.wasm as the rest of the builds (adding -lnodefs.js does not influence the wasm side of the build). Though i can tell emcc to build that, i've no clue how to test it.

Secondly, there is currently/temporarily a prebuilt copy of that at:

https://wasm-testing.sqlite.org/tmp/

(The filename will either be obvious or it will have been removed by the time you read this.)

@PabbleDabble
Copy link

Is there value to call out that this won't work in node in a persistent context in the readme?

Our app uses WebSql (only for local development) and I'm scrambling to find a replacement after reading https://developer.chrome.com/blog/sqlite-wasm-in-the-browser-backed-by-the-origin-private-file-system

I've spent a couple weeks now trying to get this or another option working, and just now got to this issue about node which seems to say that that this plugin doesn't support NODE + PERSTANCE

Thank you for the response! I don't know if there's a better 'discussion' type place to ask about this, so apologies if this is off-topic. I have a ionic/cordova/angular app that I'm trying to implement a websql replacement for my local ng serve that I run in a local browswer using localhost. Should this sqlite-wasm work in a persistent manner in an angular app?

I think I got it somewhat plumbed in to a service, but I get this message Ignoring inability to install OPFS sqlite3_vfs: The OPFS sqlite3_vfs cannot run in the main thread because it requires Atomics.wait(). or this line log('OPFS is not available, created transient database', db.filename); for the other method.

@sgbeal
Copy link
Collaborator

sgbeal commented Mar 7, 2024

Thank you for the response! I don't know if there's a better 'discussion' type place to ask about this, so apologies if this is off-topic. I have a ionic/cordova/angular app that I'm trying to implement a websql replacement for my local ng serve that I run in a local browswer using localhost. Should this sqlite-wasm work in a persistent manner in an angular app?

I think I got it somewhat plumbed in to a service, but I get this message Ignoring inability to install OPFS sqlite3_vfs: The OPFS sqlite3_vfs cannot run in the main thread because it requires Atomics.wait(). or this line log('OPFS is not available, created transient database', db.filename); for the other method.

i can't say anything useful about node-based dev processes or any given JS framework (within the sqlite project we use, write, and publish only vanilla JS), but OPFS is not available in the main thread. The only main-thread persistence option is the so-called "kvvfs," but that one has severe size limitations because it uses localStorage or sessionStorage as its back-end:

https://sqlite.org/wasm/doc/trunk/persistence.md

There is unfortunately no direct path from WebSQL to OPFS because the former is main-thread-only and OPFS is worker-only. It is possible to "remote-control" an sqlite instance from the main thread via a worker-style interface, for example:

https://sqlite.org/wasm/doc/trunk/api-worker1.md

but that particular API has severe limitations (see that link) and is not recommended for anything more than very basic/toy applications.

The recommended way to operate it from the main thread, assuming that persistent storage is needed, is to move all of your db code into a worker thread and load sqlite into that same worker thread. That gives your worker full access to the API and eliminates all of the limitations of remote-controlling it over a proxy worker. The main thread would then communicate with that worker using postMessage() and high-level app-specific messages (e.g. {op:"getCustomerData",args:[1234]} instead of {sql:"select x,y,z from customerData where id=1234"). The latter is certainly possible as well, but is arguably more difficult to maintain long-term.

@thorsten-wolf-neptune
Copy link

Thank you for the response! I don't know if there's a better 'discussion' type place to ask about this, so apologies if this is off-topic. I have a ionic/cordova/angular app that I'm trying to implement a websql replacement for my local ng serve that I run in a local browswer using localhost. Should this sqlite-wasm work in a persistent manner in an angular app?

I think I got it somewhat plumbed in to a service, but I get this message Ignoring inability to install OPFS sqlite3_vfs: The OPFS sqlite3_vfs cannot run in the main thread because it requires Atomics.wait(). or this line log('OPFS is not available, created transient database', db.filename); for the other method.

@PabbleDabble We have exactely the same requirement. We offer a Low Code development platform where customers can create cordova apps and use WebSQL to store data and went now with the SQLite WASM approach as a replacement. The existing code of our customers is all in the main thread and we were challenged to get something running also for backwards compatibility. So we are using this library together with https://github.com/magieno/sqlite-client as we (and all our low code customers) cannot easily move the logic to the service worker level. Besides some performance considerations (#61) it currently works really well.

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.

None yet

7 participants