Open joepio opened 3 years ago
The Mosaic rust project has an interesting WASM powered plugin system.
I'd like to make start on running some external code by creating a mock plugin, compiling it to wasm, registering it to an Atomic-Server and running the plugin. I'd like this to be a really minimal plugin, without any store interactions. Perhaps have an endpoint that accepts one argument, which is passed to the WASM function.
The minimal example worked fine, but now I'd really like to do things with the Store (e.g. get some data, calculate some numbers, return a new resource with these numbers), and that poses a few challenges:
Store
, but doing so is not easyStore
methods), I'm going to have to add WASM as a compilation target. #76.Maybe I should consider a scripting language such as Rhai?
I'm currently working on implementing Search, which I wanted to do as a plugin
from the start. However, my existing plugin abstraction was surely not powerful enough. For search, I need:
I should also consider using wasmedge
, which also features a JS runtime. This is interesting, as JS is such a familiar language for many devs, probably more so than any WASM compilable language.
But how would I use Atomic Data in a JS runtime?
https://github.com/second-state/wasmedge-quickjs/blob/main/example_js/http_demo.js
It's possible, but it will take a long time to develop, because I don't think I can use @tomic/lib
here. Maybe if I add a few methods for using custom fetch
functions, maybe things will work out.
See https://github.com/second-state/wasmedge-quickjs/issues/30
The wasm / wasi interface only allows for passing Vec<u8>
, so I can't do things like pass the Store
or a Resource
from rust. I can, however, pass a UTF-8 string as bytes representing a JSON-AD string, for example. I think that would be pretty quick.
WasmEdge now supports the fetch
API, which should make things easier. Definitely should try this.
Read https://www.secondstate.io/articles/embed-javascript-in-rust/
comment moved to OP
I've been playing with fp-bindgen
for a couple of hours now, and I think this feels like the most realistic approach for me so far. It works, I can move Rust strucs across the app-plugin boundary, I can export and import functions... Pretty sweet stuff.
Now I have to think about some other questions:
A Runtime
in fp-bindgen contains one WASM module. This means that we need multiple. I'd like to be able to instantiate these at runtime, for example by posting a Commit to some specific collection.
Options:
Arc<RWLock<HashMap>>
! Also fast, thread-safe. No persistence. No parsing. Really really fast.Do we want the Runtimes to persist on a database? I think that may lead to corrupt Runtimes if we update things. And it's slower. Hmm. No sled.
So it's part of the Store, I think? We clone it between threads when initializing the server. We sometimes need a lock for when we install a new plugin, but not when reading.
We need some sort of install
function. And an uninstall
function. Should it be on the Store
struct? Maybe it will make Store
too big, it already has (too) many methods.
I can define a bunch of structs and functions that will be made available to the runtime. However, I won't be able to move references across the boundary. This means that I can't pass Store
, for example, and make all functions available from there. I think every thing that I make available in the runtime, requires some manual work and possibly some code duplication.
Things we are very likely to need:
get_resource
, returns one resourcequery
returns a set of resourcessave
stores a bunch of JSON-ADatomic_lib
in pluginsOne radically different approach is to import atomic_lib
itself. This means that the runtime will have an in-memory Store. It will provide ways to create Commits, for example, and use all of Resource
's methods. But it will come at a size cost, of course.
The Zed editor now supports extensions.
extension
Trait. Mostly focused on language servers.
Being able to extend an application at runtime is something that only few systems can do. Operating Systems and Browsers are some of the few, but there are also plenty of apps that allow plugins (Raycast, VSCode, Wordpress). This is part of what makes these systems incredibly powerful and versatile. I'd love an Atomic Server to be similar to these, in some regards: it should be able to run apps, which should be able to do pretty much whatever a developer might want.
Atomic Server provides a really cool extra bonus: It runs on the web! You can access your atomic server and all its data and apps from any device.
In order to make this plugin abstraction well designed, we should extensively use it internally, during development of (core) features.
Naming: apps or plugins?
I'll mostly use plugin from now on.
Plugin or Core?
One of the hardest questions when I'm trying to extend Atomic-Server, is whether it's a Plugin, or whether it's Core functionality.
Some usecases
Let's describe some apps and try to identify which kind of abstractions that would be required to realize this.
Versioning / history #42
versions
property to existing resources, which increases discoverability)Auditing #95
Calendar app
Full text search #40
IPFS storage #66
Sys monitor
SPARQL endpoint
MemoryStore
as well as two persistent options, one using Sled (which this project uses!) and one using RocksDBSimple CRUD models endpoints
TODO MVC App
Runtime options
WASM
I'm very enthousiastic about WASM runtimes, such as Wasmer and WasmEdge. WASM is a compilation target for many languages, which means that many developers could write for it. It can be very performant, just a little short of full native. WASM executables can be shared as byte arrays. A wasm runtime can be sandboxed, and might be given access to certain system abscrations (e.g. filesystem / networking). The WASI spec aims to standardize these types of system interfaces.
Some projects for inspiration, that have implemented a WASI / WASM plugin system:
wasmer::ctx
.fp-bindgen
WASM + fp-bindgen
atomic_lib::Resource
type, as it supportsHashMap
and most low-level types. Cool stuff!.wit
based approach is the future, but fp-bindgen is nice for the short term.wasmtime + wit
.wit
files are type definitions for modules that can be used in thewasmtime
runtime to call typed WASM functions using a host-plugin system..wit
files from rust, although it's currently outdated.WASMER + WAI
https://wasmer.io/posts/wasmer-takes-webassembly-libraries-manistream-with-wai
Fork of wit-bindgen. See issue.
EXTISM
https://extism.org/
Interesting project with nice docs, but it doesn't seem to support calling host functions from within a plugin. Update: just read in discord that calling host functions is now possible in one branch. Uses unsafe, though.
JS
JS is very popular and a ton of modules can be used.
I've explored QuickJS-rs, but kind of got stuck because I don't think it's possible to limit the runtime (e.g. no network access). There is no fetch.
WasmEdge also has JS execution by porting QuickJS to WASM, but does provide ways to limit access of the runtime to networking, for example. It also does have fetch support.
I've also experimented with GreenCopperRuntime, which is really amazing, but also a bit too young to use.
Plugin API
Installation / registration
Adding a plugin should be as simple as possible. Ideally, it can be done using a web API without rebooting the server.
Installation
. This commit contains a (ipfs link to the) WASM byte array containing the logic. It optionally contains a link to a Config instance, which should be applied in another Commit.Configuring a plugin
Installation
resource has an optionalconfiguration
field, which links to aConfiguration
Resource.Removing a plugin
Updating a plugin
When the code can be called
/ipfs
Invites
, which uses query params similar to EndpointsInvite
, you need to check the Rights of the one making the Commit.Inter-plugin dependencies
versions
plugin to undo changes to calendar items.Front-end
Users are very likely to extend the views, perhaps even more than the back-end. Should a Plugin host it's own JS + HTML? Should it link to some URL of a component? Does it depend on Atomic-Data-Browser, or not per say?