An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          


Tairitsu




Generic WASM Component Runtime Engine





CI Status



Built with just




Website

|

Quick Start



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.