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/
  ├── 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.yamlservice-name.wasm
  • user-auth.yamluser-auth.wasm
  • payment-processor.yamlpayment-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.

Next: E-Commerce Tutorial →