https://github.com/celestia-island/tairitsu
Generic WASM Component Runtime Engine
https://github.com/celestia-island/tairitsu
backend engine runtime rust serverless wasm
Last synced: 6 months ago
JSON representation
Generic WASM Component Runtime Engine
- Host: GitHub
- URL: https://github.com/celestia-island/tairitsu
- Owner: celestia-island
- License: apache-2.0
- Created: 2023-10-11T03:32:14.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2026-01-07T14:28:49.000Z (6 months ago)
- Last Synced: 2026-01-13T22:57:53.104Z (6 months ago)
- Topics: backend, engine, runtime, rust, serverless, wasm
- Language: Rust
- Homepage:
- Size: 397 KB
- Stars: 6
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
Tairitsu
Generic WASM Component Runtime Engine
A WebAssembly runtime for running component-model based WASM modules.
## Features
- 🐳 **Docker-like Architecture**: Image/Container model for managing WASM modules
- 🔄 **Generic Runtime**: Does not prescribe any specific WIT interface - users define their own
- 🎯 **Flexible**: Builder pattern for custom WIT bindings and host state
- 🔌 **WIT-based**: Type-safe communication via WebAssembly Interface Types
- 🦀 **Pure Rust**: Built on Wasmtime with the Component Model
- 📦 **Macros**: Helper macros to reduce boilerplate
- 🚀 **Dynamic Invocation**: Runtime WASM function calls with RON and binary canonical ABI
- 🔤 **RON Support**: Rust-friendly serialization for better type compatibility
- ⚡ **Dual Calling Paths**: RON (convenient) + Binary (high-performance) for dynamic invocation
## Quick Start
This project uses [just](https://github.com/casey/just) as a build system.
### Run Examples
Tairitsu provides several examples demonstrating different approaches to WASM integration. Run them using `just`:
```bash
# Run comprehensive tests
just test
```
For all available commands and their descriptions, see the [justfile](justfile).
## Examples Overview
### 1. **wit-native-macro** - Macro-Assisted WIT Interface (Recommended)
The easiest way to define WIT interfaces using procedural macros:
**Features:**
- Zero boilerplate - automatic enum generation from WIT-like syntax
- Compile-time type safety with full IDE support
- No runtime serialization overhead
- Simple and intuitive API
```rust
wit_interface! {
interface filesystem {
read: func(path: String) -> Result, String>;
write: func(path: String, data: Vec) -> Result<(), String>;
delete: func(path: String) -> Result<(), String>;
list: func(directory: String) -> Result, String>;
}
}
```
### 2. **wit-native-simple** - Trait-Based WIT Interface
Manual trait implementation for maximum flexibility:
**Features:**
- Full control over interface definitions
- Composable interfaces via traits
- Zero-cost abstractions
- Interface extension support
### 3. **wit-dynamic-advanced** - Dynamic WASM Component Invocation
Advanced dynamic invocation with actual WASM Components:
**Features:**
- 🚀 Runtime guest export calls (RON + Binary)
- 📥 Host import registration and invocation
- 🔍 Runtime function discovery
- 🔤 RON serialization for Rust-friendly types
- ⚡ Binary canonical ABI for high performance
- ✅ Full support for basic and complex nested types
### 4. **wit-compile-time** - Compile-Time WIT Binding
Static WIT binding with wasmtime bindgen:
**Features:**
- Complete compile-time type checking
- Zero runtime overhead
- Best performance
- IDE autocomplete support
### 5. **wit-runtime** - Runtime WIT Loading
Dynamic WIT definition loading:
**Features:**
- Runtime WIT discovery
- Interface validation
- Plugin system support
- Capability negotiation
See [examples/README.md](examples/README.md) for detailed documentation of each example.
### Choosing the Right Approach
| Approach | Type Safety | Performance | Flexibility | Best For |
| :--- | :--- | :--- | :--- | :--- |
| **Macro** | Full | Best | Medium | Most use cases |
| **Trait** | Full | Best | High | Complex interfaces |
| **Dynamic RON/WASM** | Runtime | Best (Binary) | Highest | Plugin systems, hot-reload |
| **Compile-time** | Full | Best | Low | Fixed interfaces |
| **Runtime** | Partial | Good | High | Plugin systems |
### Basic Usage
```rust,no_run
use tairitsu::{Container, Image, HostState};
use bytes::Bytes;
// 1. Create a WASM image
let wasm_binary = std::fs::read("component.wasm")?;
let image = Image::new(Bytes::from(wasm_binary))?;
// 2. Create container with your WIT bindings
let container = Container::builder(image)?
.with_guest_initializer(|ctx| {
// Register your WIT interface (generated by wit-bindgen)
MyWit::add_to_linker(ctx.linker, |state| &mut state.my_data)?;
// Instantiate the component
let instance = MyWit::instantiate(
ctx.store,
ctx.component,
ctx.linker
)?;
Ok(GuestInstance::new(instance))
})?
.build()?;
// 3. Use the container to call into WASM
let guest = container.guest().downcast_ref::()?;
let result = guest.my_function(ctx.store)?;
```
## Architecture
Tairitsu is a **generic WASM runtime engine** - it does not prescribe any specific WIT interface:
```mermaid
graph TB
subgraph Registry["Registry"]
direction LR
Image1["Image"]
Image2["Image"]
Image3["Image"]
end
subgraph Containers["Containers"]
Container1["Container"]
Container2["Container"]
end
subgraph Container1["Container"]
direction TB
Guest["Guest (WASM)"]
Host["Host"]
Guest <-->|"User Defined
Communication"| Host
end
Registry --> Containers
Image1 -.->|"Compiled WASM components"| Container1
Image2 -.->|"Compiled WASM components"| Container1
Image3 -.->|"Compiled WASM components"| Container2
style Registry fill:#e1f5ff
style Containers fill:#fff4e1
style Container1 fill:#f0e1ff
style Container2 fill:#f0e1ff
style Guest fill:#e1ffe1
style Host fill:#ffe1f0
```
### Core Concepts
- **Registry**: Manages WASM images and running containers (like Docker daemon)
- **Image**: A compiled WASM component that can be instantiated (like Docker image)
- **Container**: A running instance with user-defined WIT bindings
- **WIT Bindings**: Users define their own WIT interfaces using `wit-bindgen`
### Defining Your WIT Interface
1. Create a `.wit` file:
```wit
interface my-app {
greet: func(name: string) -> string;
compute: func(input: string) -> string;
}
```
1. Generate bindings with `wit-bindgen`:
```bash
wit-bindgen rust --world my-app
```
1. Use the generated bindings with Tairitsu:
```rust
use tairitsu::Container;
let container = Container::builder(image)?
.with_guest_initializer(|ctx| {
// Use the generated bindings
MyApp::add_to_linker(ctx.linker, |state| &mut state.data)?;
let instance = MyApp::instantiate(ctx.store, ctx.component, ctx.linker)?;
Ok(GuestInstance::new(instance))
})?
.build()?;
```
## Helper Macros
Tairitsu provides macros to reduce boilerplate:
### `impl_wit_interface!`
Quickly implement the `WitInterface` trait:
```rust
impl_wit_interface!(MyInterface, "my-interface",
fn register_handlers(&self, dispatcher: &mut WitCommandDispatcher) {
dispatcher.register("my-command", Box::new(my_handler));
}
);
```
### `simple_handler!`
Create stateless handlers:
```rust
let handler = simple_handler!(MyCommand, |cmd| {
match cmd {
MyCommand::Foo => Ok(()),
}
});
```
### `stateful_handler!`
Create handlers with state:
```rust
let handler = stateful_handler!(MyState, MyCommand, |state, cmd| {
state.counter += 1;
Ok(state.counter)
});
```
## Advanced Usage
### Type-Safe Commands with Zero Serialization
The native approaches (`wit-native-*`) provide compile-time type safety without runtime serialization overhead:
```rust
use tairitsu::{WitCommand, WitCommandHandler, WitCommandDispatcher};
// Define your command types
#[derive(Debug, Clone)]
pub enum MyCommands {
Process { input: String },
Query { id: u32 },
}
impl WitCommand for MyCommands {
type Response = Result;
fn command_name(&self) -> &'static str {
match self {
Self::Process { .. } => "process",
Self::Query { .. } => "query",
}
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
// Implement handler
struct MyHandler;
impl WitCommandHandler for MyHandler {
fn execute(
&mut self,
command: &MyCommands,
) -> Result {
match command {
MyCommands::Process { input } => {
Ok(Ok(format!("Processed: {}", input)))
}
MyCommands::Query { id } => {
Ok(Ok(format!("Query result for {}", id)))
}
}
}
}
```
### Dynamic RON Invocation
For scenarios requiring both Rust-friendly types and high performance:
```rust
use tairitsu::{ron::{typed_ron_tool, RonToolRegistry}, Container, Image};
// RON provides Rust-native type support
#[derive(Deserialize, Serialize)]
struct CalculatorRequest {
a: i32,
b: i32,
}
#[derive(Deserialize, Serialize)]
struct CalculatorResponse {
result: i32,
}
// Create a type-safe tool with RON
let tool = typed_ron_tool("calculator", |input: CalculatorRequest| -> CalculatorResponse {
CalculatorResponse {
result: input.a + input.b,
}
});
// Register and invoke with RON syntax
let mut registry = RonToolRegistry::new();
registry.register("calculator".to_string(), tool);
// RON uses Rust-like syntax
let result = registry.invoke("calculator", "(a: 42, b: 58)")?;
// For WASM Components, use the dynamic invocation API
let mut container = Container::builder(image)?
.with_guest_initializer(|ctx| {
// ... setup WIT bindings
Ok(GuestInstance::new(instance))
})?
.build()?;
// RON path - convenient and readable
let result = container.call_guest_raw_desc(
"add",
r#"(a: 42, b: 58)"#
)?;
// Binary path - high performance
use wasmtime::component::Val;
let args = vec![Val::S32(42), Val::S32(58)];
let results = container.call_guest_binary("add", &args)?;
```
**RON Type Support:**
| Type | RON Syntax | Description |
| ---- | ---------- | ----------- |
| **Struct** | `Struct { field: value }` | Named fields |
| **Tuple** | `(value1, value2)` | Ordered values |
| **Enum** | `Variant(value)` | Enum with payload |
| **Option** | `Some(value)` / `None` | Optional values |
| **Result** | `Ok(value)` / `Err(error)` | Fallible operations |
| **List** | `[item1, item2]` | Homogeneous arrays |
| **Unit** | `()` | Empty tuple |
### Composable Interfaces
Use trait objects to compose multiple WIT interfaces:
```rust
use tairitsu::{CompositeWitInterface, WitInterface};
struct FileSystemInterface;
impl WitInterface for FileSystemInterface {
fn interface_name(&self) -> &'static str {
"filesystem"
}
fn register_handlers(&self, dispatcher: &mut WitCommandDispatcher) {
// Register handlers
}
}
struct NetworkInterface;
impl WitInterface for NetworkInterface {
fn interface_name(&self) -> &'static str {
"network"
}
fn register_handlers(&self, dispatcher: &mut WitCommandDispatcher) {
// Register handlers
}
}
// Compose multiple interfaces
let mut composite = CompositeWitInterface::new();
composite.add_interface(Box::new(FileSystemInterface));
composite.add_interface(Box::new(NetworkInterface));
// Register all at once
let mut dispatcher = WitCommandDispatcher::new();
composite.register_all(&mut dispatcher);
```
## Design Philosophy
Tairitsu is designed to be a **pure engine** - it provides:
- ✅ Generic WASM container runtime
- ✅ Image/Registry management
- ✅ Builder pattern for WIT bindings
- ✅ Helper macros to reduce boilerplate
It does **not** prescribe:
- ❌ Specific WIT interfaces (you define your own)
- ❌ Command types (you define your own)
- ❌ Serialization format (use WIT)
This makes Tairitsu suitable for **any** WASM component-based application.
## Architecture Trade-offs
### Native vs Dynamic Approaches
**Native (Trait-based)** - Use when:
- You control both host and guest code
- Performance is critical
- Type safety is a priority
- Building a closed system
**Dynamic RON/WASM** - Use when:
- Plugin systems with hot-reload required
- Performance-critical dynamic calls needed
- Rust-friendly serialization preferred
- Full bidirectional guest-host communication
**Choosing Between RON and Binary Paths:**
| Path | Type Safety | Performance | Use Case |
| ---- | ----------- | ----------- | -------- |
| **RON** | Runtime | Good | Convenient, debugging, cross-language |
| **Binary** | Runtime | Best | Hot loops, frequent calls, zero-copy |
| **Static** | Compile-time | Best | Known interfaces, maximum type safety |
### When to Use Each Approach
#### Macro Approach (`wit-native-macro`)
- ✅ Quick development with minimal boilerplate
- ✅ Best for most applications
- ✅ Easy to maintain and refactor
- ❌ Less control than manual trait implementation
#### Manual Trait Approach (`wit-native-simple`)
- ✅ Maximum flexibility and control
- ✅ Best for complex interface hierarchies
- ✅ Can optimize for specific use cases
- ❌ More boilerplate to maintain
#### Dynamic RON/WASM Approach (`wit-dynamic-advanced`)
- ✅ Ideal for plugin systems with hot-reload
- ✅ Supports actual WASM Component execution
- ✅ RON for Rust-friendly serialization
- ✅ Binary path for high-performance calls
- ✅ Bidirectional guest-host communication
- ❌ More complex than static approaches
- ❌ Requires WASM Components (not just tools)
## Best Practices
1. **Start with the macro approach** - It's the easiest way to get started
2. **Use traits for composition** - Combine multiple interfaces using `CompositeWitInterface`
3. **Prefer native approaches** - Use static API for known interfaces
4. **Use RON for dynamic calls** - Rust-friendly types with runtime flexibility
5. **Use binary path for hot loops** - When performance is critical
6. **Define clear interfaces** - Well-designed WIT interfaces make your system more maintainable
7. **Test in isolation** - Test handlers independently before integrating with WASM
## What's New in 0.3.0
- 🚀 **Dynamic WASM Component Invocation**
- Runtime guest export calls with `call_guest_raw_desc()` (RON)
- High-performance binary calls with `call_guest_binary()`
- Host import registration and invocation
- Runtime function discovery
- 🔤 **RON Serialization Support**
- `RonBinding` and `RonToolRegistry` for Rust-friendly types
- Native Rust enum, tuple, option, and result support
- `typed_ron_tool!` macro for type-safe tools
- ⚡ **Dual Calling Paths**
- RON path: Convenient, human-readable serialization
- Binary path: Zero-copy, canonical ABI performance
- ✅ **Complete Type Support**
- All basic types (Bool, Integers, Floats, String, Char)
- Complex types (List, Tuple, Record, Variant, Result, Option)
- Full nested type support (e.g., `List>`, `Option>`)
- 📦 **New Example**: `wit-dynamic-advanced` showcasing all new features
See [examples/README.md](examples/README.md) for migration guide and detailed examples.
## What is Tairitsu?
**Tairitsu** (対立) carries a dual meaning:
1. **From Arcaea**: Named after the character Tairitsu from the rhythm game Arcaea, representing the "Conflict" side
2. **Opposition & Duality**: Reflects the architectural concept of this framework - the inherent duality and opposition between the WASM virtual machine (guest) and the host environment, connected through WIT (WebAssembly Interface Types)
## License
See [LICENSE](LICENSE) for details.