Skip to main content

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 };