atomicdata-dev / atomic-server

An open source headless CMS / real-time database. Powerful table editor, full-text search, and SDKs for JS / React / Svelte.
https://atomicserver.eu
MIT License
877 stars 42 forks source link

Atomic Plugins, Apps, Store #73

Open joepio opened 3 years ago

joepio commented 3 years ago

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

Auditing #95

Calendar app

Full text search #40

IPFS storage #66

Sys monitor

SPARQL endpoint

Simple 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:

WASM + fp-bindgen

wasmtime + wit

WASMER + WAI

https://wasmer.io/posts/wasmer-takes-webassembly-libraries-manistream-with-wai

Fork of wit-bindgen. See issue.

A key difference between WAI and wit-bindgen is the focus on stability - people should be able to start using WAI right now.

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.

Configuring a plugin

Removing a plugin

Updating a plugin

When the code can be called

Inter-plugin dependencies

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?

joepio commented 3 years ago

The Mosaic rust project has an interesting WASM powered plugin system.

joepio commented 3 years ago

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.

joepio commented 3 years ago

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:

Maybe I should consider a scripting language such as Rhai?

joepio commented 2 years ago

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:

joepio commented 2 years ago

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?

Use wasmedge's HTTP library

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

WASI interface

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.

joepio commented 2 years ago

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/

joepio commented 2 years ago

comment moved to OP

joepio commented 2 years ago

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:

Where to store runtimes

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:

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.

What to expose to the Runtime

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:

Use atomic_lib in plugins

One 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.

joepio commented 1 year ago

https://wasmer.io/posts/wasmer-takes-webassembly-libraries-manistream-with-wai

joepio commented 1 month ago

The Zed editor now supports extensions.