wasm_link/lib.rs
1//! A WebAssembly plugin runtime for building modular applications.
2//!
3//! Plugins are small, single-purpose WASM components that connect through abstract
4//! interfaces. Each plugin declares a **plug** (the interface it implements) and
5//! zero or more **sockets** (interfaces it depends on). `wasm_link` links these
6//! into a dependency tree and handles cross-plugin dispatch.
7//!
8//! # Core Concepts
9//!
10//! - **Interface**: A contract declaring what an implementer exports and what a
11//! consumer may import. Defined via [`InterfaceData`].
12//!
13//! - **Plug**: A plugin's declaration that it implements an interface (exports its functions).
14//!
15//! - **Socket**: A plugin's declaration that it depends on an interface (expects to call
16//! into implementations provided by other plugins).
17//!
18//! - **Cardinality**: Each interface specifies how many plugins may implement it via
19//! [`InterfaceCardinality`]. This affects how dispatch results are returned.
20//!
21//! - **Root Socket**: The entry point interface that the host application calls into.
22//! Other interfaces are internal - only accessible to plugins, not the host.
23//!
24//! # Example
25//! ```
26//! use wasm_link::{
27//! InterfaceData, InterfaceCardinality, FunctionData, ReturnKind,
28//! PluginCtxView, PluginData, PluginTree, Socket,
29//! Engine, Component, Linker, ResourceTable, Val,
30//! };
31//!
32//! // Declare your fixture sources
33//! #[derive( Clone )]
34//! struct Func { name: String, return_kind: ReturnKind }
35//! impl FunctionData for Func {
36//! fn name( &self ) -> &str { self.name.as_str() }
37//! fn return_kind( &self ) -> ReturnKind { self.return_kind.clone() }
38//! // Determine whether a function is a resource method
39//! // a constructor is not considered to be a method
40//! fn is_method( &self ) -> bool { false }
41//! }
42//!
43//! struct Interface { id: &'static str, funcs: Vec<Func> }
44//! impl InterfaceData for Interface {
45//! type Id = &'static str ;
46//! type Error = std::convert::Infallible ;
47//! type Function = Func ;
48//! type FunctionIter<'a> = std::slice::Iter<'a, Func> ;
49//! type ResourceIter<'a> = std::iter::Empty<&'a String> ;
50//! fn id( &self ) -> Result<&Self::Id, Self::Error> { Ok( &self.id ) }
51//! fn cardinality( &self ) -> Result<&InterfaceCardinality, Self::Error> { Ok( &InterfaceCardinality::ExactlyOne ) }
52//! fn package_name( &self ) -> Result<&str, Self::Error> { Ok( "my:package/example" ) }
53//! fn functions( &self ) -> Result<Self::FunctionIter<'_>, Self::Error> { Ok( self.funcs.iter()) }
54//! fn resources( &self ) -> Result<Self::ResourceIter<'_>, Self::Error> { Ok( std::iter::empty()) }
55//! }
56//!
57//! struct Plugin { id: &'static str, plug: &'static str, resource_table: ResourceTable }
58//! impl PluginCtxView for Plugin {
59//! fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
60//! }
61//! impl PluginData for Plugin {
62//! type Id = &'static str ;
63//! type InterfaceId = &'static str ;
64//! type Error = std::convert::Infallible ;
65//! type SocketIter<'a> = std::iter::Empty<&'a Self::InterfaceId> ;
66//! fn id( &self ) -> Result<&Self::Id, Self::Error> { Ok( &self.id ) }
67//! fn plug( &self ) -> Result<&Self::InterfaceId, Self::Error> { Ok( &self.plug ) }
68//! fn sockets( &self ) -> Result<Self::SocketIter<'_>, Self::Error> { Ok( std::iter::empty()) }
69//! fn component( &self, engine: &Engine ) -> Result<Component, Self::Error> {
70//! /* initialise your component here */
71//! # Ok( Component::new( engine, r#"(component
72//! # (core module $m (func (export "f") (result i32) i32.const 42))
73//! # (core instance $i (instantiate $m))
74//! # (func $f (export "get-value") (result u32) (canon lift (core func $i "f")))
75//! # (instance $inst (export "get-value" (func $f)))
76//! # (export "my:package/example" (instance $inst))
77//! # )"# ).unwrap())
78//! }
79//! }
80//!
81//! // Now construct some plugins and related data
82//! let root_interface_id = "root" ;
83//! let plugins = [ Plugin { id: "foo", plug: root_interface_id, resource_table: ResourceTable::new() }];
84//! let interfaces = [ Interface { id: root_interface_id, funcs: vec![
85//! Func { name: "get-value".to_string(), return_kind: ReturnKind::MayContainResources }
86//! ]}];
87//!
88//! // First you need to tell wasm_link about your plugins, interfaces and where you want
89//! // the execution to begin. wasm_link will try it's best to load in all the plugins,
90//! // upon encountering an error, it will try to salvage as much of the remaining data
91//! // as possible returning a list of failures alongside the `PluginTree`.
92//! let ( tree, build_errors ) = PluginTree::new( root_interface_id, interfaces, plugins );
93//! assert!( build_errors.is_empty() );
94//!
95//! // Once you've got your `PluginTree` constructed, you can link the plugins together
96//! // Since some plugins may fail to load, it is only at this point that the cardinality
97//! // requirements are satisfied by the plugins that managed to get loaded, otherwise it
98//! // tries to salvage as much of the tree as can be loaded returning a list of failures
99//! // alongside the loaded `PluginTreeHead` - the root node of the `PluginTree`.
100//! let engine = Engine::default();
101//! let linker = Linker::new( &engine );
102//! let ( tree_head, load_errors ) = tree.load( &engine, &linker ).unwrap();
103//! assert!( load_errors.is_empty() );
104//!
105//! // Now you can dispatch any function on the root interface.
106//! // This will dispatch the function for all plugins plugged in to the root socket returning
107//! // a Result for each in the shape determined by the interface cardinality.
108//! let result = tree_head.dispatch( "my:package/example", "get-value", true, &[] );
109//! match result {
110//! Socket::ExactlyOne( Ok( Val::U32( n ))) => assert_eq!( n, 42 ),
111//! Socket::ExactlyOne( Err( e )) => panic!( "dispatch error: {e}" ),
112//! _ => panic!( "unexpected cardinality" ),
113//! }
114//! ```
115
116mod interface ;
117mod plugin ;
118mod loading ;
119mod plugin_tree ;
120mod plugin_tree_head ;
121mod socket ;
122mod plugin_instance ;
123mod utils ;
124
125pub use wasmtime::Engine ;
126pub use wasmtime::component::{ Component, Linker, ResourceTable, Val };
127
128pub use interface::{ InterfaceData, InterfaceCardinality, FunctionData, ReturnKind };
129pub use plugin::{ PluginCtxView, PluginData };
130pub use loading::LoadError ;
131pub use plugin_tree::{ PluginTree, PluginTreeError };
132pub use plugin_tree_head::PluginTreeHead ;
133pub use socket::Socket ;
134pub use plugin_instance::DispatchError ;
135pub use utils::{ PartialSuccess, PartialResult };