A
WebAssembly
plugin runtime based around
Wasmtime
intended for building fully modular applications
- Unlimited plugin composition with no architectural restrictions
- Performant language-agnostic plugin system
NOTE: Async types (Future, Stream, ErrorContext) are not yet supported for cross-plugin transfer and will return an error if encountered.
- Single tool, single task: Apps should be broken up into small chunks that are meant to be composed together to create a whole.
- Build around your workflow, not services: Everything you use for a single task should be working together instead of you trying to duct-tape it together yourself.
- The client belongs to the user: Any part should be able to be easily added, removed or switched out for something else.
- Zero-trust by default: Don't just use something and expect it behaves, assume malice and constrain it to the minimum capabilities required.
use std::collections::{ HashMap, HashSet };
use wasm_link::{
Binding, Interface, Function, FunctionKind, ReturnKind,
Plugin, PluginContext, Engine, Component, Linker, ResourceTable, Val,
};
use wasm_link::cardinality::ExactlyOne ;
// First, declare a plugin context, the data stored inside wasmtime `Store<T>`.
// It must contain a resource table to implement `PluginContext` which is needed
// for ownership tracking of wasm component model resources.
struct Context { resource_table: ResourceTable }
impl PluginContext for Context {
fn resource_table( &mut self ) -> &mut ResourceTable {
&mut self.resource_table
}
}
// You create your own engine. This allows you to define your config but note that
// not all options are compatible. As a general rule of thumb, if an option changes
// the way you interact with wasm, it is likely not compatible since this is managed
// by `wasm_link` directly. If the option makes sense, it will likely be supported
// in the future through wasm_link options.
let engine = Engine::default();
// Similarly you may create your own linker, which you can add any exports into.
// Such exports will be available to all the plugins. It is your responsibility to
// make sure these don't conflict with re-exports of plugins that some other plugin
// depends on as these too have to be added to the same linker.
let linker = Linker::new( &engine );
// Build the DAG bottom-up: start with plugins that have no dependencies.
// Note that for plugins that don't require linking, you only need to pass in
// a reference to a linker. For plugins that have dependencies, the linker is mutated.
// Plugin IDs are specified in the cardinality wrapper to prevent duplicate ids.
let leaf = Plugin::new(
Component::new( &engine, "(component)" )?,
Context { resource_table: ResourceTable::new() },
).instantiate( &engine, &linker )?;
// Bindings expose a plugin's exports to other plugins.
// Wrapper sets cardinality: ExactlyOne, AtMostOne (0-1), AtLeastOne (1+), Any (0+).
let leaf_binding = Binding::new(
"empty:package",
HashMap::new(),
ExactlyOne( "leaf".to_string(), leaf ),
);
// `link()` wires up dependencies - this plugin can now import from leaf_binding.
let root = Plugin::new(
Component::new( &engine, r#"(component
(core module $m (func (export "f") (result i32) i32.const 42))
(core instance $i (instantiate $m))
(func $f (export "get-value") (result u32) (canon lift (core func $i "f")))
(instance $inst (export "get-value" (func $f)))
(export "my:package/example" (instance $inst))
)"# )?,
Context { resource_table: ResourceTable::new() },
).link( &engine, linker, vec![ leaf_binding ])?;
// Interface tells `wasm_link` which functions exist and how to handle returns.
let root_binding = Binding::new(
"my:package",
HashMap::from([( "example".to_string(), Interface::new(
HashMap::from([( "get-value".into(), Function::new(
FunctionKind::Freestanding,
ReturnKind::MayContainResources,
))]),
HashSet::new(),
))]),
ExactlyOne( "root".to_string(), root ),
);
// Now you can call into the plugin graph from the host.
let result = root_binding.dispatch( "example", "get-value", &[ /* args */ ] )?;
match result {
ExactlyOne( _id, Ok( Val::U32( n ))) => assert_eq!( n, 42 ),
ExactlyOne( _id, Ok( _ )) => panic!( "unexpected response" ),
ExactlyOne( _id, Err( err )) => panic!( "dispatch error: {}", err ),
}- ✅ Basic plugin linking
- ✅ Component model support
- ✅ Resource support
- ✅ Fuel, epoch, and memory resource limits
- ⬛ Async, streams and threads
Further goals are yet to be determined.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributions are welcome. For major changes, please open an issue first for discussion.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
If you have Nix installed, please use the provided flake.nix to set up everything you need:
nix developYou should most likely be fine with just the Rust toolchain