WASM Modules
WebAssembly (WASM) modules enable server-side execution of compiled code in PrioStack. WASM provides near-native performance while maintaining security through sandboxing. Services can include WASM modules to handle complex business logic, data processing, or performance-critical operations.
💡 Key Concept: WASM modules allow you to write high-performance business logic in languages like Rust, C++, or Go, while maintaining the safety and portability of the PrioStack platform.
Why WASM?
WASM offers several advantages for server-side execution in PrioStack:
Performance
- Near-native speed: WASM executes at speeds close to native machine code
- Predictable performance: No garbage collection pauses or JIT compilation delays
- Small binaries: Compact representation reduces deployment size
Security
- Sandboxed execution: WASM runs in a secure sandbox with limited system access
- Memory safety: Prevents buffer overflows and memory corruption
- Deterministic: Same input always produces same output
Language Support
- Multiple languages: Write in Rust, C++, Go, C#, and more
- Existing codebases: Reuse existing libraries and algorithms
- Tooling maturity: Leverage mature development tools and ecosystems
WASM Module Structure
WASM modules in PrioStack follow a specific structure and naming convention. Each module corresponds to a service and contains the compiled business logic.
File Organization
├── bundle.yaml
├── 📁 services/
│ ├── user-service.yaml
│ └── order-service.yaml
└── 📁 wasm/
├── user-service.wasm
└── order-service.wasm
Naming Convention
WASM modules should be named after their corresponding service:
service-name.yaml→service-name.wasmuser-auth.yaml→user-auth.wasmpayment-processor.yaml→payment-processor.wasm
Function Interface
WASM modules expose functions that can be called from service configurations. These functions follow a specific interface for integration with PrioStack.
Function Signature
WASM functions use a standardized interface for input and output:
// Function signature (in target language)
fn process_data(input: &[u8]) -> Result
Input/Output Format
Functions receive and return data as byte arrays. PrioStack handles serialization:
- Input: JSON data serialized to bytes
- Output: Function returns JSON-serialized result
- Errors: Functions can return error codes or messages
Supported Languages
PrioStack supports WASM compilation from several popular languages:
Rust
Excellent performance and memory safety. Recommended for most use cases.
// Cargo.toml
[package]
name = "user-service"
version = "0.1.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
C++
High performance with extensive libraries. Good for computational workloads.
// CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(user_service)
set(CMAKE_CXX_STANDARD 17)
add_executable(user_service main.cpp)
target_link_libraries(user_service emscripten)
Go
Simple and productive. Good for web services and APIs.
// go.mod
module user-service
go 1.19
// +build js,wasm
package main
import "syscall/js"
AssemblyScript
TypeScript-like syntax that compiles to WASM. Easier learning curve.
// package.json
{
"name": "user-service",
"scripts": {
"build": "asc main.ts -b build.wasm"
},
"devDependencies": {
"assemblyscript": "^0.21.0"
}
}
Development Workflow
Developing WASM modules follows a standard compile-and-deploy cycle:
1. Write Code
Implement your business logic in your chosen language using the appropriate WASM bindings.
2. Compile to WASM
Use language-specific tools to compile your code to WASM format:
# Rust
wasm-pack build --target web
# C++
emcc main.cpp -o output.wasm
# Go
GOOS=js GOARCH=wasm go build -o output.wasm
# AssemblyScript
npx asc main.ts -b output.wasm
3. Test Locally
Test your WASM module using language-specific testing frameworks before deployment.
4. Deploy
Place the compiled .wasm file in your bundle's wasm/ directory and deploy the bundle.
API Integration
WASM functions integrate seamlessly with PrioStack's API system:
HTTP Endpoints
api:
basePath: "/api/v1/users"
endpoints:
- name: createUser
method: POST
path: "/"
to: wasm:user-service:createUser
Intent Handlers
intents:
- name: validateUser
whenSensor: userCreated
toFunc: wasm:user-service:validateUserData
Runtime Environment
WASM modules run in a controlled environment with access to specific resources:
Available Resources
- Memory: Configurable memory allocation
- Time: Access to current time and timers
- Random: Cryptographically secure random number generation
- Logging: Structured logging capabilities
Security Restrictions
- No file system access: Cannot read/write files
- No network access: Cannot make external HTTP calls
- No system calls: Cannot execute system commands
- Limited memory: Memory usage is bounded
Best Practices
Performance
- Minimize allocations: Reuse memory where possible
- Use appropriate data structures: Choose efficient algorithms
- Profile regularly: Monitor performance characteristics
- Optimize hot paths: Focus optimization on frequently called functions
Security
- Validate inputs: Always validate function inputs
- Handle errors gracefully: Don't expose internal errors
- Use safe languages: Prefer memory-safe languages like Rust
- Keep dependencies updated: Use current versions of dependencies
Development
- Write tests: Test WASM functions thoroughly
- Use type safety: Leverage language type systems
- Document interfaces: Clearly document function contracts
- Version carefully: Plan for interface changes
Example: User Service in Rust
Here's a complete example of a user service implemented as a WASM module:
Cargo.toml
[package]
name = "user-service"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
src/lib.rs
use wasm_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User {
id: String,
email: String,
name: String,
}
#[derive(Serialize, Deserialize)]
struct CreateUserRequest {
email: String,
name: String,
}
#[wasm_bindgen]
pub fn create_user(input: &[u8]) -> Vec<u8> {
// Parse input JSON
let request: CreateUserRequest = match serde_json::from_slice(input) {
Ok(req) => req,
Err(_) => return b"{\"error\": \"Invalid input\"}".to_vec(),
};
// Validate input
if request.email.is_empty() || request.name.is_empty() {
return b"{\"error\": \"Email and name are required\"}".to_vec();
}
// Create user (in real implementation, this would save to database)
let user = User {
id: "user_123".to_string(),
email: request.email,
name: request.name,
};
// Return JSON response
match serde_json::to_vec(&user) {
Ok(json) => json,
Err(_) => b"{\"error\": \"Serialization failed\"}".to_vec(),
}
}
Service Configuration
name: user-service
description: User management service implemented in Rust WASM
api:
basePath: "/api/v1/users"
endpoints:
- name: createUser
method: POST
path: "/"
to: wasm:user-service:createUser
goals:
- name: responseTime
metric: latency
target: "< 50ms"
description: WASM functions should respond quickly
✅ Pro Tip: Start with simple WASM functions and gradually add complexity. Use the performance benefits of WASM for CPU-intensive operations while keeping simple logic in YAML configurations.