
Cloudflare Workers Multi Lang
- 206 installs
- 202 repo stars
- Updated August 4, 2026
- secondsky/claude-skills
Use cloudflare-workers-multi-lang for development tasks
About
cloudflare-workers-multi-lang: A skill for development. This provides functionality for development workflows.
- cloudflare-workers-multi-lang
Cloudflare Workers Multi Lang by the numbers
- 206 all-time installs (skills.sh)
- +10 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,899 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/secondsky/claude-skills --skill cloudflare-workers-multi-langAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 206 |
|---|---|
| repo stars | ★ 202 |
| Last updated | August 4, 2026 |
| Repository | secondsky/claude-skills ↗ |
What it does
Use cloudflare-workers-multi-lang for development tasks
Files
Multi-Language Workers Development
Build Cloudflare Workers using Rust, Python, or WebAssembly for performance-critical operations.
Language Comparison
| Feature | JavaScript/TS | Rust | Python |
|---|---|---|---|
| Startup | Fast | Fastest (WASM) | Moderate |
| CPU Perf | Good | Excellent | Good |
| Memory | Higher | Lower | Higher |
| Bundle Size | Smaller | Medium | Larger |
| Type Safety | Optional (TS) | Strict | Optional |
| Best For | General apps | CPU-intensive | Data/ML |
Quick Decision
Need maximum performance? → Rust/WASM
Heavy computation (crypto, image processing)? → Rust/WASM
Data processing, ML inference? → Python
General web apps? → JavaScript/TypeScriptTop 10 Multi-Lang Errors
| Error | Language | Cause | Solution |
|---|---|---|---|
WebAssembly.instantiate() failed | Rust | Invalid WASM | Check wasm-pack build output |
Module parse failed: Unexpected token | Rust | ESM/CJS mismatch | Use --target bundler |
Cannot find module | Python | Missing dep | Add to pyproject.toml |
Out of memory | All | Large WASM | Enable streaming instantiation |
Exceeded CPU time limit | All | Long computation | Chunk processing |
wasm-bindgen version mismatch | Rust | Dep conflict | Align versions in Cargo.toml |
RuntimeError: unreachable | Rust | Panic in WASM | Add proper error handling |
TypeError: not a function | Rust | Missing export | Add #[wasm_bindgen] attribute |
Python worker startup timeout | Python | Slow init | Minimize imports |
SharedArrayBuffer not supported | All | Security | Add COOP/COEP headers |
Rust Quick Start
# Install tools
cargo install wasm-pack
# Create project
cargo new --lib my-worker
cd my-worker
# Add to Cargo.toml
cat >> Cargo.toml << 'EOF'
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
worker = "0.3"
console_error_panic_hook = "0.1"
[profile.release]
opt-level = "s"
lto = true
EOF// src/lib.rs
use worker::*;
#[event(fetch)]
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
console_error_panic_hook::set_once();
Router::new()
.get("/", |_, _| Response::ok("Hello from Rust!"))
.get("/compute", |_, _| {
// CPU-intensive computation
let result = heavy_computation();
Response::ok(format!("Result: {}", result))
})
.run(req, env)
.await
}
fn heavy_computation() -> u64 {
(1..1_000_000).filter(|n| is_prime(*n)).count() as u64
}
fn is_prime(n: u64) -> bool {
if n < 2 { return false; }
(2..=(n as f64).sqrt() as u64).all(|i| n % i != 0)
}Python Quick Start (Workers for Platforms)
# pyproject.toml
[project]
name = "my-worker"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"# src/entry.py
from js import Response, Headers
async def on_fetch(request, env):
url = request.url
if "/compute" in url:
result = heavy_computation()
return Response.new(f"Result: {result}")
return Response.new("Hello from Python!")
def heavy_computation():
"""CPU-intensive computation"""
return sum(1 for n in range(2, 100000) if is_prime(n))
def is_prime(n):
if n < 2:
return False
return all(n % i != 0 for i in range(2, int(n**0.5) + 1))WASM Module Integration
// Load and use WASM module in TypeScript Worker
import wasmModule from './pkg/my_lib_bg.wasm';
import { init, process_data } from './pkg/my_lib';
let wasmInstance: WebAssembly.Instance;
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Initialize WASM once
if (!wasmInstance) {
wasmInstance = await WebAssembly.instantiate(wasmModule);
init();
}
// Use WASM function
const result = process_data(inputData);
return Response.json({ result });
},
};When to Load References
| Reference | Load When |
|---|---|
references/rust-workers.md | Building Workers with Rust/WASM |
references/python-workers.md | Using Python on Workers for Platforms |
references/wasm-integration.md | Integrating WASM modules in any Worker |
Performance Tips
1. WASM Initialization: Cache instance, use streaming 2. Memory: Use typed arrays for data transfer 3. Bundle Size: Enable LTO, strip debug info 4. Cold Starts: Keep WASM modules small 5. Data Transfer: Minimize JS/WASM boundary crossings
See Also
workers-performance- General optimization techniquesworkers-testing- Testing multi-language Workerscloudflare-worker-base- Basic Workers setup
Python Workers Development
Build Cloudflare Workers using Python with Pyodide runtime.
Overview
Python Workers run on Pyodide (Python compiled to WebAssembly), enabling data science, ML inference, and Python library usage at the edge.
Capabilities
- Python 3.12: Full Python standard library
- NumPy/Pandas: Data processing (pre-bundled)
- ML Inference: TensorFlow.js, scikit-learn models
- Cloudflare Bindings: Full access to KV, D1, R2, etc.
Limitations
- Cold Start: ~500ms-1s for first request
- Bundle Size: Larger than JS/Rust
- CPU Limits: Same 50ms/10ms limits apply
- No Threading: Single-threaded execution
Project Setup
# Create project
mkdir my-python-worker
cd my-python-worker
# Initialize
cat > pyproject.toml << 'EOF'
[project]
name = "my-python-worker"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
EOF
# Create source
mkdir src
touch src/entry.py
# Wrangler config
cat > wrangler.toml << 'EOF'
name = "my-python-worker"
main = "src/entry.py"
compatibility_date = "2024-12-01"
compatibility_flags = ["python_workers"]
EOFProject Structure
my-python-worker/
├── src/
│ ├── entry.py # Main worker
│ ├── handlers.py # Route handlers
│ ├── utils.py # Utilities
│ └── models/ # ML models
├── pyproject.toml
├── wrangler.toml
└── requirements.txtBasic Worker
# src/entry.py
from js import Response, Headers, JSON
async def on_fetch(request, env):
"""Main fetch handler"""
url = request.url
method = request.method
# Routing
if "/api/data" in url:
return await handle_api(request, env)
elif "/api/process" in url and method == "POST":
return await handle_process(request, env)
elif "/health" in url:
return Response.new("OK", status=200)
return Response.new("Hello from Python Worker!", status=200)
async def handle_api(request, env):
"""Handle API requests"""
data = {"message": "Hello from Python", "status": "ok"}
headers = Headers.new({"Content-Type": "application/json"}.items())
return Response.new(JSON.stringify(data), headers=headers)
async def handle_process(request, env):
"""Handle POST with JSON body"""
try:
body = await request.json()
result = process_data(body)
return Response.json(result)
except Exception as e:
return Response.json({"error": str(e)}, status=400)
def process_data(data):
"""Process incoming data"""
return {
"processed": True,
"input": data,
"result": compute_result(data)
}
def compute_result(data):
"""CPU-intensive computation"""
if isinstance(data, dict) and "value" in data:
return data["value"] * 2
return 0Request Handling
# src/handlers.py
from js import Response, Headers, URL
import json
async def get_query_params(request):
"""Parse query parameters"""
url = URL.new(request.url)
params = {}
for entry in url.searchParams.entries():
params[entry[0]] = entry[1]
return params
async def get_json_body(request):
"""Parse JSON body"""
text = await request.text()
return json.loads(text)
async def handle_users_list(request, env):
"""GET /api/users"""
params = await get_query_params(request)
page = int(params.get("page", 1))
limit = int(params.get("limit", 10))
# Access D1 database
db = env.DB
result = await db.prepare(
"SELECT * FROM users LIMIT ? OFFSET ?"
).bind(limit, (page - 1) * limit).all()
return Response.json({
"data": result.results,
"page": page,
"limit": limit
})
async def handle_user_create(request, env):
"""POST /api/users"""
try:
body = await get_json_body(request)
# Validation
if not body.get("name"):
return Response.json(
{"error": "Name is required"},
status=400
)
if not body.get("email") or "@" not in body["email"]:
return Response.json(
{"error": "Valid email is required"},
status=400
)
# Insert into D1
db = env.DB
result = await db.prepare(
"INSERT INTO users (name, email) VALUES (?, ?) RETURNING *"
).bind(body["name"], body["email"]).first()
return Response.json(result, status=201)
except Exception as e:
return Response.json({"error": str(e)}, status=500)
async def handle_user_by_id(request, env, user_id):
"""GET /api/users/:id"""
db = env.DB
user = await db.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(user_id).first()
if not user:
return Response.json(
{"error": "User not found"},
status=404
)
return Response.json(user)Cloudflare Bindings
# src/bindings.py
from js import Response
async def use_kv(env):
"""KV Namespace operations"""
kv = env.MY_KV
# Write
await kv.put("key", "value")
# Read
value = await kv.get("key")
# Read with metadata
result = await kv.getWithMetadata("key", type="text")
value = result.value
metadata = result.metadata
# List keys
keys = await kv.list(prefix="user:", limit=100)
# Delete
await kv.delete("key")
return value
async def use_d1(env):
"""D1 Database operations"""
db = env.DB
# Single query
result = await db.prepare("SELECT * FROM users").all()
users = result.results
# Parameterized query
user = await db.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(123).first()
# Insert
await db.prepare(
"INSERT INTO users (name, email) VALUES (?, ?)"
).bind("John", "john@example.com").run()
# Batch operations
statements = [
db.prepare("INSERT INTO logs (msg) VALUES (?)").bind("Log 1"),
db.prepare("INSERT INTO logs (msg) VALUES (?)").bind("Log 2"),
]
results = await db.batch(statements)
return users
async def use_r2(env):
"""R2 Bucket operations"""
bucket = env.MY_BUCKET
# Upload
await bucket.put("file.txt", "Hello World")
# Upload with metadata
await bucket.put("image.png", image_bytes, {
"httpMetadata": {"contentType": "image/png"},
"customMetadata": {"author": "me"}
})
# Download
obj = await bucket.get("file.txt")
if obj:
content = await obj.text()
# List objects
objects = await bucket.list(prefix="files/", limit=100)
# Delete
await bucket.delete("file.txt")
return content
async def use_environment(env):
"""Environment variables and secrets"""
# Regular variable
api_url = env.API_URL
# Secret (same access pattern)
api_key = env.API_KEY
return api_urlData Processing with NumPy
# src/data_processing.py
import numpy as np
from js import Response
import json
async def handle_statistics(request, env):
"""Compute statistics on numeric data"""
body = await request.json()
data = body.get("data", [])
if not data:
return Response.json({"error": "No data provided"}, status=400)
# Convert to numpy array
arr = np.array(data, dtype=np.float64)
# Compute statistics
stats = {
"count": len(arr),
"mean": float(np.mean(arr)),
"std": float(np.std(arr)),
"min": float(np.min(arr)),
"max": float(np.max(arr)),
"median": float(np.median(arr)),
"percentiles": {
"25": float(np.percentile(arr, 25)),
"50": float(np.percentile(arr, 50)),
"75": float(np.percentile(arr, 75)),
"90": float(np.percentile(arr, 90)),
"99": float(np.percentile(arr, 99)),
}
}
return Response.json(stats)
async def handle_matrix_operations(request, env):
"""Matrix operations"""
body = await request.json()
matrix_a = np.array(body.get("a", []))
matrix_b = np.array(body.get("b", []))
result = {}
if body.get("operation") == "multiply":
result["product"] = np.dot(matrix_a, matrix_b).tolist()
elif body.get("operation") == "add":
result["sum"] = (matrix_a + matrix_b).tolist()
elif body.get("operation") == "inverse":
result["inverse"] = np.linalg.inv(matrix_a).tolist()
return Response.json(result)
def normalize_data(data):
"""Normalize data to 0-1 range"""
arr = np.array(data)
min_val = np.min(arr)
max_val = np.max(arr)
return ((arr - min_val) / (max_val - min_val)).tolist()ML Inference
# src/ml_inference.py
import json
from js import Response
# Simple model inference (no external deps)
class SimpleClassifier:
def __init__(self, weights):
self.weights = weights
def predict(self, features):
score = sum(w * f for w, f in zip(self.weights, features))
return 1 if score > 0 else 0
# Pre-trained model weights (stored in KV or as constant)
MODEL_WEIGHTS = [0.5, -0.3, 0.2, 0.1]
async def handle_predict(request, env):
"""Run ML prediction"""
body = await request.json()
features = body.get("features", [])
if len(features) != len(MODEL_WEIGHTS):
return Response.json(
{"error": f"Expected {len(MODEL_WEIGHTS)} features"},
status=400
)
model = SimpleClassifier(MODEL_WEIGHTS)
prediction = model.predict(features)
return Response.json({
"prediction": prediction,
"class": "positive" if prediction == 1 else "negative"
})
# Load model from KV
async def load_model(env):
"""Load model weights from KV"""
kv = env.MODELS_KV
weights_json = await kv.get("classifier-weights")
if weights_json:
return json.loads(weights_json)
return MODEL_WEIGHTSError Handling
# src/errors.py
from js import Response
import traceback
class AppError(Exception):
def __init__(self, message, status_code=500):
self.message = message
self.status_code = status_code
super().__init__(message)
class ValidationError(AppError):
def __init__(self, message):
super().__init__(message, 400)
class NotFoundError(AppError):
def __init__(self, message="Not found"):
super().__init__(message, 404)
async def error_handler(handler):
"""Wrap handler with error handling"""
async def wrapper(request, env):
try:
return await handler(request, env)
except ValidationError as e:
return Response.json({"error": e.message}, status=400)
except NotFoundError as e:
return Response.json({"error": e.message}, status=404)
except AppError as e:
return Response.json({"error": e.message}, status=e.status_code)
except Exception as e:
# Log full traceback
print(traceback.format_exc())
return Response.json(
{"error": "Internal server error"},
status=500
)
return wrapperWrangler Configuration
# wrangler.toml
name = "my-python-worker"
main = "src/entry.py"
compatibility_date = "2024-12-01"
compatibility_flags = ["python_workers"]
[vars]
ENVIRONMENT = "development"
[[kv_namespaces]]
binding = "MY_KV"
id = "xxx"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxx"
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"
[env.production]
vars = { ENVIRONMENT = "production" }Development and Deployment
# Local development
npx wrangler dev
# Deploy
npx wrangler deploy
# View logs
npx wrangler tailPerformance Tips
1. Minimize imports: Only import what you need 2. Lazy loading: Import heavy modules in handlers 3. Cache computations: Store results in KV 4. Use NumPy: Vectorized operations are faster 5. Batch operations: Combine D1 queries 6. Avoid large responses: Stream if possible
Rust Workers Development
Build high-performance Cloudflare Workers with Rust and WebAssembly.
Why Rust for Workers?
- Performance: Near-native speed via WebAssembly
- Memory Safety: No garbage collection pauses
- Type Safety: Compile-time guarantees
- Small Bundles: Optimized WASM output
- CPU-Intensive: Ideal for crypto, parsing, computation
Project Setup
# Prerequisites
rustup target add wasm32-unknown-unknown
cargo install wasm-pack worker-build
# Create project
npx wrangler generate my-rust-worker https://github.com/cloudflare/workers-sdk/tree/main/templates/experimental/worker-rust
# Or manual setup
cargo new --lib my-worker
cd my-workerProject Structure
my-worker/
├── src/
│ ├── lib.rs # Main worker code
│ ├── utils.rs # Utility functions
│ └── types.rs # Type definitions
├── Cargo.toml
├── wrangler.jsonc
└── build.shCargo.toml Configuration
[package]
name = "my-worker"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
# Cloudflare Workers SDK
worker = "0.3"
# WASM bindings
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
# Error handling
console_error_panic_hook = "0.1"
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Async
futures = "0.3"
# Optional: HTTP client
reqwest = { version = "0.12", default-features = false, features = ["json"] }
[profile.release]
opt-level = "s" # Optimize for size
lto = true # Link-time optimization
codegen-units = 1 # Better optimization
panic = "abort" # Smaller panic handling
strip = true # Strip symbolsWrangler Configuration
// wrangler.jsonc
{
"name": "my-rust-worker",
"main": "build/worker/shim.mjs",
"compatibility_date": "2024-12-01",
"build": {
"command": "cargo install -q worker-build && worker-build --release"
}
}Basic Worker
// src/lib.rs
use worker::*;
#[event(fetch)]
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
// Set up panic hook for debugging
console_error_panic_hook::set_once();
// Router for handling different paths
Router::new()
.get("/", handle_index)
.get("/api/data", handle_api)
.post("/api/process", handle_process)
.run(req, env)
.await
}
async fn handle_index(_req: Request, _ctx: RouteContext<()>) -> Result<Response> {
Response::ok("Hello from Rust Worker!")
}
async fn handle_api(_req: Request, ctx: RouteContext<()>) -> Result<Response> {
// Access environment bindings
let kv = ctx.kv("MY_KV")?;
let value = kv.get("key").text().await?;
Response::ok(value.unwrap_or_default())
}
async fn handle_process(mut req: Request, _ctx: RouteContext<()>) -> Result<Response> {
let body: serde_json::Value = req.json().await?;
let processed = process_data(&body);
Response::from_json(&processed)
}
fn process_data(data: &serde_json::Value) -> serde_json::Value {
// CPU-intensive processing
serde_json::json!({
"processed": true,
"input": data,
})
}Request Handling
use worker::*;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: String,
name: String,
email: String,
}
async fn handle_create_user(mut req: Request, ctx: RouteContext<()>) -> Result<Response> {
// Parse JSON body
let input: CreateUserRequest = match req.json().await {
Ok(data) => data,
Err(_) => return Response::error("Invalid JSON", 400),
};
// Validate
if input.name.is_empty() {
return Response::error("Name is required", 400);
}
if !input.email.contains('@') {
return Response::error("Invalid email", 400);
}
// Access D1 database
let db = ctx.env.d1("DB")?;
let id = uuid::Uuid::new_v4().to_string();
db.prepare("INSERT INTO users (id, name, email) VALUES (?, ?, ?)")
.bind(&[id.clone().into(), input.name.clone().into(), input.email.clone().into()])?
.run()
.await?;
let user = User {
id,
name: input.name,
email: input.email,
};
Response::from_json(&user).map(|r| r.with_status(201))
}
// Query parameters
async fn handle_search(req: Request, _ctx: RouteContext<()>) -> Result<Response> {
let url = req.url()?;
let query: std::collections::HashMap<_, _> = url.query_pairs().collect();
let search_term = query.get("q").map(|s| s.as_ref()).unwrap_or("");
let page: u32 = query.get("page")
.and_then(|p| p.parse().ok())
.unwrap_or(1);
Response::ok(format!("Search: {}, Page: {}", search_term, page))
}
// Path parameters
async fn handle_user_by_id(_req: Request, ctx: RouteContext<()>) -> Result<Response> {
let id = ctx.param("id").unwrap();
let db = ctx.env.d1("DB")?;
let result = db.prepare("SELECT * FROM users WHERE id = ?")
.bind(&[id.into()])?
.first::<User>(None)
.await?;
match result {
Some(user) => Response::from_json(&user),
None => Response::error("User not found", 404),
}
}Cloudflare Bindings
use worker::*;
async fn handle_bindings(req: Request, ctx: RouteContext<()>) -> Result<Response> {
// KV Namespace
let kv = ctx.kv("MY_KV")?;
kv.put("key", "value")?.execute().await?;
let value = kv.get("key").text().await?;
// D1 Database
let db = ctx.env.d1("DB")?;
let results = db.prepare("SELECT * FROM users")
.all()
.await?;
// R2 Bucket
let bucket = ctx.bucket("MY_BUCKET")?;
bucket.put("file.txt", "content".as_bytes().to_vec()).execute().await?;
let object = bucket.get("file.txt").execute().await?;
// Durable Object
let namespace = ctx.durable_object("COUNTER")?;
let id = namespace.id_from_name("my-counter")?;
let stub = id.get_stub()?;
let response = stub.fetch_with_str("/increment").await?;
// Environment variables
let api_key: String = ctx.env.var("API_KEY")?.to_string();
let secret: String = ctx.env.secret("SECRET")?.to_string();
Response::ok("Bindings accessed successfully")
}Durable Objects in Rust
use worker::*;
#[durable_object]
pub struct Counter {
state: State,
env: Env,
count: u64,
}
#[durable_object]
impl DurableObject for Counter {
fn new(state: State, env: Env) -> Self {
Self {
state,
env,
count: 0,
}
}
async fn fetch(&mut self, req: Request) -> Result<Response> {
// Load state
self.count = self.state.storage().get("count").await?.unwrap_or(0);
let url = req.url()?;
let path = url.path();
match path {
"/increment" => {
self.count += 1;
self.state.storage().put("count", self.count).await?;
Response::ok(format!("Count: {}", self.count))
}
"/decrement" => {
self.count = self.count.saturating_sub(1);
self.state.storage().put("count", self.count).await?;
Response::ok(format!("Count: {}", self.count))
}
"/get" => {
Response::ok(format!("Count: {}", self.count))
}
_ => Response::error("Not Found", 404),
}
}
}Error Handling
use worker::*;
// Custom error type
#[derive(Debug)]
enum AppError {
Validation(String),
Database(String),
NotFound,
}
impl From<AppError> for worker::Error {
fn from(e: AppError) -> Self {
worker::Error::RustError(format!("{:?}", e))
}
}
// Result type alias
type AppResult<T> = std::result::Result<T, AppError>;
async fn handle_with_errors(req: Request, ctx: RouteContext<()>) -> Result<Response> {
match process_request(req, ctx).await {
Ok(response) => Ok(response),
Err(AppError::Validation(msg)) => {
Response::error(msg, 400)
}
Err(AppError::NotFound) => {
Response::error("Not found", 404)
}
Err(AppError::Database(msg)) => {
console_log!("Database error: {}", msg);
Response::error("Internal server error", 500)
}
}
}
async fn process_request(mut req: Request, ctx: RouteContext<()>) -> AppResult<Response> {
let body: serde_json::Value = req.json().await
.map_err(|_| AppError::Validation("Invalid JSON".into()))?;
if body.get("name").is_none() {
return Err(AppError::Validation("Name required".into()));
}
Ok(Response::ok("Success").unwrap())
}CPU-Intensive Tasks
use worker::*;
// Image processing example
async fn handle_image_process(mut req: Request, _ctx: RouteContext<()>) -> Result<Response> {
let bytes = req.bytes().await?;
// Process in chunks to avoid CPU limit
let processed = process_image(&bytes)?;
Response::from_bytes(processed)
}
fn process_image(data: &[u8]) -> Result<Vec<u8>> {
// Use image crate for processing
// Note: Add `image = "0.24"` to Cargo.toml
Ok(data.to_vec()) // Placeholder
}
// Cryptography example
fn hash_data(data: &[u8]) -> String {
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();
hex::encode(result)
}
// JSON parsing (faster than JS for large payloads)
fn parse_large_json(data: &str) -> Result<serde_json::Value> {
serde_json::from_str(data)
.map_err(|e| worker::Error::RustError(e.to_string()))
}Testing
// tests/integration.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process_data() {
let input = serde_json::json!({"value": 42});
let output = process_data(&input);
assert!(output.get("processed").unwrap().as_bool().unwrap());
}
#[test]
fn test_validation() {
let result = validate_email("test@example.com");
assert!(result.is_ok());
let result = validate_email("invalid");
assert!(result.is_err());
}
}Build and Deploy
# Development
npx wrangler dev
# Build only
worker-build --release
# Deploy
npx wrangler deploy
# Check bundle size
ls -lh build/worker/Optimization Tips
1. Use `#[inline]` for small, hot functions 2. Enable LTO in Cargo.toml 3. Strip debug info with strip = true 4. Use `opt-level = "s"` for size optimization 5. Avoid `panic!` - use Result for error handling 6. Minimize allocations - use references when possible 7. Use `#[cold]` for error paths
WebAssembly Integration in Workers
Integrate high-performance WASM modules into JavaScript/TypeScript Workers.
Why WASM in Workers?
- Performance: Near-native speed for CPU-intensive tasks
- Language Choice: Use Rust, C/C++, Go, AssemblyScript
- Existing Code: Port libraries from other languages
- Security: Sandboxed execution with memory isolation
Module Types
| Type | Use Case | Size | Startup |
|---|---|---|---|
| Rust | General-purpose, crypto | Medium | Fast |
| AssemblyScript | TypeScript developers | Small | Fastest |
| C/C++ | Legacy code, codecs | Variable | Fast |
| Go | Existing Go code | Large | Slower |
Basic Integration
Importing WASM Module
// Import WASM module (bundled with Worker)
import wasmModule from './lib.wasm';
// Or import with bindings
import init, { process_data } from './pkg/my_lib.js';
let wasmInitialized = false;
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Initialize WASM once
if (!wasmInitialized) {
await init(wasmModule);
wasmInitialized = true;
}
// Use WASM function
const result = process_data("input");
return new Response(result);
},
};Streaming Instantiation (Large Modules)
// For modules >4KB, use streaming instantiation
import wasmModule from './large-lib.wasm';
let wasmInstance: WebAssembly.Instance | null = null;
async function getWasmInstance(): Promise<WebAssembly.Instance> {
if (!wasmInstance) {
// Streaming compilation (more efficient)
const response = new Response(wasmModule, {
headers: { 'Content-Type': 'application/wasm' },
});
const { instance } = await WebAssembly.instantiateStreaming(
response,
{
env: {
// Import functions the WASM module needs
abort: () => { throw new Error('WASM abort'); },
log: (ptr: number, len: number) => {
// Handle logging from WASM
},
},
}
);
wasmInstance = instance;
}
return wasmInstance;
}
export default {
async fetch(request: Request): Promise<Response> {
const wasm = await getWasmInstance();
const exports = wasm.exports as WasmExports;
const result = exports.compute(42);
return Response.json({ result });
},
};
interface WasmExports {
compute: (n: number) => number;
memory: WebAssembly.Memory;
}Data Transfer
Passing Numbers
// Numbers transfer directly
const result = wasmExports.add(10, 20); // Returns 30
// Typed numbers
const floatResult = wasmExports.sqrt(16.0); // Returns 4.0Passing Strings
// Strings require memory allocation
function stringToWasm(str: string, memory: WebAssembly.Memory, alloc: (size: number) => number): number {
const encoder = new TextEncoder();
const bytes = encoder.encode(str);
// Allocate memory in WASM
const ptr = alloc(bytes.length + 1);
// Write bytes to WASM memory
const view = new Uint8Array(memory.buffer, ptr, bytes.length + 1);
view.set(bytes);
view[bytes.length] = 0; // Null terminator
return ptr;
}
function stringFromWasm(ptr: number, memory: WebAssembly.Memory): string {
const view = new Uint8Array(memory.buffer);
let end = ptr;
while (view[end] !== 0) end++;
const decoder = new TextDecoder();
return decoder.decode(view.slice(ptr, end));
}
// Usage
const ptr = stringToWasm("Hello", wasm.memory, wasm.alloc);
const resultPtr = wasm.process_string(ptr);
const result = stringFromWasm(resultPtr, wasm.memory);
wasm.dealloc(ptr); // Free input memory
wasm.dealloc(resultPtr); // Free output memoryPassing Arrays (Typed Arrays)
// Efficient array transfer via shared memory
function arrayToWasm(
arr: Float64Array,
memory: WebAssembly.Memory,
alloc: (size: number) => number
): { ptr: number; len: number } {
const byteLength = arr.byteLength;
const ptr = alloc(byteLength);
// Copy array to WASM memory
const view = new Float64Array(memory.buffer, ptr, arr.length);
view.set(arr);
return { ptr, len: arr.length };
}
function arrayFromWasm(
ptr: number,
len: number,
memory: WebAssembly.Memory
): Float64Array {
// Create view of WASM memory (zero-copy)
return new Float64Array(memory.buffer, ptr, len);
}
// Usage
const data = new Float64Array([1.0, 2.0, 3.0, 4.0, 5.0]);
const { ptr, len } = arrayToWasm(data, wasm.memory, wasm.alloc);
const resultPtr = wasm.compute_statistics(ptr, len);
const stats = arrayFromWasm(resultPtr, 4, wasm.memory); // [mean, std, min, max]
// Free memory when done
wasm.dealloc(ptr);
wasm.dealloc(resultPtr);Passing Objects (JSON)
// Objects transfer as JSON strings
interface UserData {
name: string;
age: number;
scores: number[];
}
function objectToWasm(obj: UserData, wasm: WasmExports): number {
const json = JSON.stringify(obj);
return stringToWasm(json, wasm.memory, wasm.alloc);
}
function objectFromWasm(ptr: number, wasm: WasmExports): UserData {
const json = stringFromWasm(ptr, wasm.memory);
return JSON.parse(json);
}
// Usage
const user: UserData = { name: "Alice", age: 30, scores: [95, 87, 92] };
const inputPtr = objectToWasm(user, wasm);
const resultPtr = wasm.process_user(inputPtr);
const result = objectFromWasm(resultPtr, wasm);wasm-bindgen Integration (Rust)
// With wasm-bindgen, data transfer is handled automatically
import init, {
greet,
process_array,
UserProcessor,
} from './pkg/my_rust_lib.js';
import wasmModule from './pkg/my_rust_lib_bg.wasm';
let initialized = false;
export default {
async fetch(request: Request): Promise<Response> {
if (!initialized) {
await init(wasmModule);
initialized = true;
}
// Strings work directly
const greeting = greet("World"); // "Hello, World!"
// Arrays work directly (cloned across boundary)
const numbers = new Float64Array([1, 2, 3, 4, 5]);
const sum = process_array(numbers);
// Classes work directly
const processor = new UserProcessor();
processor.add_user("Alice", 30);
processor.add_user("Bob", 25);
const users = processor.get_users(); // Returns JSON string
processor.free(); // Clean up Rust memory
return Response.json({ greeting, sum, users: JSON.parse(users) });
},
};// Corresponding Rust code
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
#[wasm_bindgen]
pub fn process_array(data: &[f64]) -> f64 {
data.iter().sum()
}
#[wasm_bindgen]
pub struct UserProcessor {
users: Vec<(String, u32)>,
}
#[wasm_bindgen]
impl UserProcessor {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self { users: Vec::new() }
}
pub fn add_user(&mut self, name: &str, age: u32) {
self.users.push((name.to_string(), age));
}
pub fn get_users(&self) -> String {
serde_json::to_string(&self.users).unwrap()
}
}AssemblyScript Integration
// AssemblyScript provides TypeScript-like syntax
// assembly/index.ts
export function add(a: i32, b: i32): i32 {
return a + b;
}
export function processArray(ptr: usize, len: i32): f64 {
let sum: f64 = 0;
for (let i = 0; i < len; i++) {
sum += load<f64>(ptr + i * 8);
}
return sum / len;
}
// Worker code
import * as loader from '@assemblyscript/loader';
import wasmModule from './build/release.wasm';
let wasmExports: typeof loader.ASUtil & {
add: (a: number, b: number) => number;
processArray: (ptr: number, len: number) => number;
};
export default {
async fetch(request: Request): Promise<Response> {
if (!wasmExports) {
const instance = await loader.instantiate(wasmModule);
wasmExports = instance.exports;
}
const result = wasmExports.add(10, 20);
// For arrays, use __newArray helper
const data = [1.0, 2.0, 3.0, 4.0, 5.0];
const ptr = wasmExports.__newArray(wasmExports.__getArrayId(), data);
const avg = wasmExports.processArray(ptr, data.length);
return Response.json({ result, avg });
},
};Performance Optimization
Minimize Boundary Crossings
// BAD: Many small calls
for (const item of items) {
results.push(wasm.process(item)); // Slow!
}
// GOOD: Batch processing
const inputPtr = arrayToWasm(items, wasm.memory, wasm.alloc);
const outputPtr = wasm.process_batch(inputPtr, items.length);
const results = arrayFromWasm(outputPtr, items.length, wasm.memory);Reuse Memory Allocations
// Allocate once, reuse
class WasmProcessor {
private inputBuffer: number;
private outputBuffer: number;
private bufferSize: number;
constructor(private wasm: WasmExports, maxSize: number) {
this.bufferSize = maxSize;
this.inputBuffer = wasm.alloc(maxSize * 8); // Float64
this.outputBuffer = wasm.alloc(maxSize * 8);
}
process(data: Float64Array): Float64Array {
if (data.length > this.bufferSize) {
throw new Error('Data exceeds buffer size');
}
// Copy to pre-allocated buffer
const input = new Float64Array(
this.wasm.memory.buffer,
this.inputBuffer,
data.length
);
input.set(data);
// Process
this.wasm.process(this.inputBuffer, this.outputBuffer, data.length);
// Return view (zero-copy)
return new Float64Array(
this.wasm.memory.buffer,
this.outputBuffer,
data.length
);
}
dispose() {
this.wasm.dealloc(this.inputBuffer);
this.wasm.dealloc(this.outputBuffer);
}
}Use SharedArrayBuffer (When Available)
// Check for SharedArrayBuffer support
const hasSharedMemory = typeof SharedArrayBuffer !== 'undefined';
// With shared memory, workers can share data without copying
if (hasSharedMemory) {
const sharedMemory = new WebAssembly.Memory({
initial: 256,
maximum: 512,
shared: true,
});
// Pass to WASM instantiation
const { instance } = await WebAssembly.instantiate(wasmModule, {
env: { memory: sharedMemory },
});
}Error Handling
// Wrap WASM calls with error handling
function safeWasmCall<T>(fn: () => T, fallback: T): T {
try {
return fn();
} catch (error) {
if (error instanceof WebAssembly.RuntimeError) {
console.error('WASM runtime error:', error.message);
// Common: unreachable executed, out of bounds memory access
} else if (error instanceof WebAssembly.CompileError) {
console.error('WASM compile error:', error.message);
} else {
console.error('Unknown WASM error:', error);
}
return fallback;
}
}
// Usage
const result = safeWasmCall(() => wasm.compute(input), 0);Handling Panics (Rust)
// In Rust, set up panic hook
use std::panic;
#[wasm_bindgen(start)]
pub fn init() {
panic::set_hook(Box::new(console_error_panic_hook::hook));
}
// Or handle Result types
#[wasm_bindgen]
pub fn safe_compute(input: &str) -> Result<String, JsValue> {
match process(input) {
Ok(result) => Ok(result),
Err(e) => Err(JsValue::from_str(&e.to_string())),
}
}// In TypeScript, catch as exceptions
try {
const result = wasm.safe_compute(input);
} catch (error) {
// Error thrown from Rust
console.error('Rust error:', error);
}Wrangler Configuration
// wrangler.jsonc
{
"name": "wasm-worker",
"main": "src/index.ts",
"compatibility_date": "2024-12-01",
"rules": [
{
"type": "CompiledWasm",
"globs": ["**/*.wasm"],
"fallthrough": true
}
]
}Build Scripts
Rust Build
#!/bin/bash
# build-rust.sh
# Build with wasm-pack
wasm-pack build --target bundler --release
# Optimize WASM size
wasm-opt -Oz -o pkg/optimized.wasm pkg/my_lib_bg.wasm
# Report size
ls -lh pkg/*.wasmAssemblyScript Build
#!/bin/bash
# build-assemblyscript.sh
# Compile AssemblyScript
npx asc assembly/index.ts \
--target release \
--optimize \
--exportRuntime \
-o build/release.wasm
# Report size
ls -lh build/*.wasmCommon Errors
| Error | Cause | Solution |
|---|---|---|
unreachable executed | Panic in Rust | Add console_error_panic_hook |
out of bounds memory | Invalid pointer | Check array bounds |
CompileError | Invalid WASM | Rebuild with correct target |
LinkError: import not found | Missing import | Provide required imports |
memory access out of bounds | Buffer overflow | Increase memory or check sizes |
Best Practices
1. Initialize Once: Cache WASM instance at module level 2. Batch Operations: Minimize JS/WASM boundary crossings 3. Use Typed Arrays: Most efficient for numeric data 4. Pre-allocate Buffers: Reuse memory for repeated operations 5. Handle Errors: Wrap WASM calls with try/catch 6. Optimize Size: Use wasm-opt, enable LTO 7. Measure Performance: Profile to find bottlenecks
#!/bin/bash
# Build Rust Worker
#
# Prerequisites:
# rustup target add wasm32-unknown-unknown
# cargo install wasm-pack worker-build
#
# Usage:
# ./scripts/build-rust.sh [--release|--dev]
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Defaults
MODE="release"
OPTIMIZE=true
VERBOSE=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--dev|--debug)
MODE="debug"
OPTIMIZE=false
shift
;;
--release)
MODE="release"
shift
;;
--no-optimize)
OPTIMIZE=false
shift
;;
--verbose|-v)
VERBOSE=true
shift
;;
--help|-h)
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " --dev, --debug Build in debug mode"
echo " --release Build in release mode (default)"
echo " --no-optimize Skip wasm-opt optimization"
echo " --verbose, -v Show detailed output"
echo " --help, -h Show this help"
exit 0
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
exit 1
;;
esac
done
echo -e "${BLUE}=== Rust Worker Build ===${NC}"
echo -e "Mode: ${YELLOW}${MODE}${NC}"
echo ""
# Check prerequisites
echo -e "${BLUE}Checking prerequisites...${NC}"
if ! command -v rustc &> /dev/null; then
echo -e "${RED}Error: Rust not installed${NC}"
echo "Install from: https://rustup.rs/"
exit 1
fi
if ! command -v wasm-pack &> /dev/null; then
echo -e "${YELLOW}Installing wasm-pack...${NC}"
cargo install wasm-pack
fi
if ! rustup target list --installed | grep -q wasm32-unknown-unknown; then
echo -e "${YELLOW}Adding wasm32-unknown-unknown target...${NC}"
rustup target add wasm32-unknown-unknown
fi
echo -e "${GREEN}Prerequisites OK${NC}"
echo ""
# Check for Cargo.toml
if [ ! -f "Cargo.toml" ]; then
echo -e "${RED}Error: Cargo.toml not found${NC}"
echo "Run this script from your Rust Worker project root"
exit 1
fi
# Build with wasm-pack
echo -e "${BLUE}Building with wasm-pack...${NC}"
if [ "$MODE" = "release" ]; then
if $VERBOSE; then
wasm-pack build --target bundler --release
else
wasm-pack build --target bundler --release 2>&1 | grep -E "(Compiling|Finished|Optimizing|warning:|error)"
fi
else
if $VERBOSE; then
wasm-pack build --target bundler --dev
else
wasm-pack build --target bundler --dev 2>&1 | grep -E "(Compiling|Finished|warning:|error)"
fi
fi
echo -e "${GREEN}Build complete${NC}"
echo ""
# Optimize with wasm-opt (release only)
if $OPTIMIZE && [ "$MODE" = "release" ]; then
if command -v wasm-opt &> /dev/null; then
echo -e "${BLUE}Optimizing WASM with wasm-opt...${NC}"
# Find the WASM file
WASM_FILE=$(find pkg -name "*_bg.wasm" -type f | head -1)
if [ -n "$WASM_FILE" ]; then
ORIGINAL_SIZE=$(stat -f%z "$WASM_FILE" 2>/dev/null || stat -c%s "$WASM_FILE")
ORIGINAL_KB=$((ORIGINAL_SIZE / 1024))
# Optimize for size
wasm-opt -Oz -o "${WASM_FILE}.opt" "$WASM_FILE"
mv "${WASM_FILE}.opt" "$WASM_FILE"
OPTIMIZED_SIZE=$(stat -f%z "$WASM_FILE" 2>/dev/null || stat -c%s "$WASM_FILE")
OPTIMIZED_KB=$((OPTIMIZED_SIZE / 1024))
SAVED_KB=$((ORIGINAL_KB - OPTIMIZED_KB))
PERCENT=$((SAVED_KB * 100 / ORIGINAL_KB))
echo -e "${GREEN}Optimized: ${ORIGINAL_KB}KB → ${OPTIMIZED_KB}KB (-${SAVED_KB}KB, -${PERCENT}%)${NC}"
fi
else
echo -e "${YELLOW}wasm-opt not found, skipping optimization${NC}"
echo "Install with: cargo install wasm-opt"
fi
echo ""
fi
# Build worker-build shim
if command -v worker-build &> /dev/null; then
echo -e "${BLUE}Building worker shim...${NC}"
if [ "$MODE" = "release" ]; then
worker-build --release
else
worker-build --dev
fi
echo -e "${GREEN}Worker shim built${NC}"
echo ""
else
echo -e "${YELLOW}worker-build not found, skipping shim generation${NC}"
echo "Install with: cargo install worker-build"
echo ""
fi
# Report sizes
echo -e "${BLUE}Build artifacts:${NC}"
echo ""
if [ -d "pkg" ]; then
echo "pkg/ (wasm-pack output):"
ls -lh pkg/*.wasm 2>/dev/null | awk '{print " " $9 " (" $5 ")"}'
ls -lh pkg/*.js 2>/dev/null | awk '{print " " $9 " (" $5 ")"}'
fi
if [ -d "build" ]; then
echo ""
echo "build/ (worker-build output):"
ls -lh build/worker/*.mjs 2>/dev/null | awk '{print " " $9 " (" $5 ")"}'
ls -lh build/worker/*.wasm 2>/dev/null | awk '{print " " $9 " (" $5 ")"}'
fi
echo ""
echo -e "${GREEN}Build complete!${NC}"
echo ""
echo "Next steps:"
echo " 1. Test locally: npx wrangler dev"
echo " 2. Deploy: npx wrangler deploy"
#!/bin/bash
# Build and Optimize WASM Module
#
# Supports: Rust (wasm-pack), AssemblyScript, C/C++ (Emscripten)
#
# Prerequisites:
# - Rust: rustup, wasm-pack, wasm-opt
# - AssemblyScript: @assemblyscript/loader
# - C/C++: Emscripten SDK
#
# Usage:
# ./scripts/build-wasm.sh [--rust|--as|--c] [--release|--dev]
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# Defaults
LANG=""
MODE="release"
OPTIMIZE=true
OUTPUT_DIR="build"
VERBOSE=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--rust)
LANG="rust"
shift
;;
--as|--assemblyscript)
LANG="assemblyscript"
shift
;;
--c|--cpp|--emscripten)
LANG="c"
shift
;;
--dev|--debug)
MODE="debug"
OPTIMIZE=false
shift
;;
--release)
MODE="release"
shift
;;
--no-optimize)
OPTIMIZE=false
shift
;;
--output|-o)
OUTPUT_DIR="$2"
shift 2
;;
--verbose|-v)
VERBOSE=true
shift
;;
--help|-h)
echo "Usage: $0 [options]"
echo ""
echo "Language (auto-detected if not specified):"
echo " --rust Build Rust to WASM"
echo " --as Build AssemblyScript to WASM"
echo " --c, --cpp Build C/C++ to WASM (Emscripten)"
echo ""
echo "Options:"
echo " --dev, --debug Build in debug mode"
echo " --release Build in release mode (default)"
echo " --no-optimize Skip wasm-opt optimization"
echo " --output, -o DIR Output directory (default: build)"
echo " --verbose, -v Show detailed output"
echo " --help, -h Show this help"
exit 0
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
exit 1
;;
esac
done
# Auto-detect language
if [ -z "$LANG" ]; then
if [ -f "Cargo.toml" ]; then
LANG="rust"
elif [ -f "asconfig.json" ] || [ -d "assembly" ]; then
LANG="assemblyscript"
elif [ -f "CMakeLists.txt" ] || ls *.c *.cpp >/dev/null 2>&1; then
LANG="c"
else
echo -e "${RED}Error: Could not auto-detect language${NC}"
echo "Specify with --rust, --as, or --c"
exit 1
fi
fi
echo -e "${BLUE}=== WASM Build ===${NC}"
echo -e "Language: ${CYAN}${LANG}${NC}"
echo -e "Mode: ${YELLOW}${MODE}${NC}"
echo -e "Output: ${OUTPUT_DIR}/"
echo ""
# Create output directory
mkdir -p "$OUTPUT_DIR"
# ==========================================
# RUST BUILD
# ==========================================
build_rust() {
echo -e "${BLUE}Building Rust to WASM...${NC}"
# Check prerequisites
if ! command -v rustc &> /dev/null; then
echo -e "${RED}Error: Rust not installed${NC}"
exit 1
fi
if ! command -v wasm-pack &> /dev/null; then
echo -e "${YELLOW}Installing wasm-pack...${NC}"
cargo install wasm-pack
fi
if ! rustup target list --installed | grep -q wasm32-unknown-unknown; then
rustup target add wasm32-unknown-unknown
fi
# Build
if [ "$MODE" = "release" ]; then
wasm-pack build --target bundler --release --out-dir "$OUTPUT_DIR/pkg"
else
wasm-pack build --target bundler --dev --out-dir "$OUTPUT_DIR/pkg"
fi
# Find and report WASM file
WASM_FILE=$(find "$OUTPUT_DIR/pkg" -name "*_bg.wasm" -type f | head -1)
echo -e "${GREEN}Built: $WASM_FILE${NC}"
}
# ==========================================
# ASSEMBLYSCRIPT BUILD
# ==========================================
build_assemblyscript() {
echo -e "${BLUE}Building AssemblyScript to WASM...${NC}"
# Check prerequisites
if ! command -v npx &> /dev/null; then
echo -e "${RED}Error: Node.js/npm not installed${NC}"
exit 1
fi
# Check for asc
if ! npx asc --version &> /dev/null; then
echo -e "${YELLOW}Installing AssemblyScript...${NC}"
npm install --save-dev assemblyscript @assemblyscript/loader
fi
# Find entry file
ENTRY=""
if [ -f "assembly/index.ts" ]; then
ENTRY="assembly/index.ts"
elif [ -f "src/index.ts" ]; then
ENTRY="src/index.ts"
else
echo -e "${RED}Error: Could not find AssemblyScript entry file${NC}"
exit 1
fi
# Build
if [ "$MODE" = "release" ]; then
npx asc "$ENTRY" \
--target release \
--optimize \
--exportRuntime \
-o "$OUTPUT_DIR/module.wasm" \
-t "$OUTPUT_DIR/module.wat"
else
npx asc "$ENTRY" \
--target debug \
--debug \
--exportRuntime \
-o "$OUTPUT_DIR/module.wasm" \
-t "$OUTPUT_DIR/module.wat"
fi
WASM_FILE="$OUTPUT_DIR/module.wasm"
echo -e "${GREEN}Built: $WASM_FILE${NC}"
}
# ==========================================
# C/C++ BUILD (Emscripten)
# ==========================================
build_c() {
echo -e "${BLUE}Building C/C++ to WASM...${NC}"
# Check prerequisites
if ! command -v emcc &> /dev/null; then
echo -e "${RED}Error: Emscripten not installed${NC}"
echo "Install from: https://emscripten.org/docs/getting_started/downloads.html"
exit 1
fi
# Find source files
SOURCES=""
if [ -f "CMakeLists.txt" ]; then
# Use CMake
echo -e "${BLUE}Building with CMake...${NC}"
mkdir -p build-cmake
cd build-cmake
emcmake cmake .. -DCMAKE_BUILD_TYPE=$([ "$MODE" = "release" ] && echo "Release" || echo "Debug")
cmake --build .
cp *.wasm "../$OUTPUT_DIR/" 2>/dev/null || true
cd ..
WASM_FILE=$(find "$OUTPUT_DIR" -name "*.wasm" | head -1)
else
# Direct compilation
if ls *.c >/dev/null 2>&1; then
SOURCES=$(ls *.c | tr '\n' ' ')
elif ls *.cpp >/dev/null 2>&1; then
SOURCES=$(ls *.cpp | tr '\n' ' ')
elif ls src/*.c >/dev/null 2>&1; then
SOURCES=$(ls src/*.c | tr '\n' ' ')
elif ls src/*.cpp >/dev/null 2>&1; then
SOURCES=$(ls src/*.cpp | tr '\n' ' ')
else
echo -e "${RED}Error: No C/C++ source files found${NC}"
exit 1
fi
echo "Sources: $SOURCES"
OPTS="-s WASM=1 -s EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s MODULARIZE=1"
if [ "$MODE" = "release" ]; then
OPTS="$OPTS -O3 -s STANDALONE_WASM=1"
else
OPTS="$OPTS -O0 -g"
fi
emcc $SOURCES $OPTS -o "$OUTPUT_DIR/module.js"
WASM_FILE="$OUTPUT_DIR/module.wasm"
fi
echo -e "${GREEN}Built: $WASM_FILE${NC}"
}
# ==========================================
# BUILD
# ==========================================
case $LANG in
rust)
build_rust
;;
assemblyscript)
build_assemblyscript
;;
c)
build_c
;;
esac
echo ""
# ==========================================
# OPTIMIZE
# ==========================================
if $OPTIMIZE && [ "$MODE" = "release" ]; then
if command -v wasm-opt &> /dev/null; then
echo -e "${BLUE}Optimizing WASM...${NC}"
# Find all WASM files
for wasm in $(find "$OUTPUT_DIR" -name "*.wasm" -type f); do
ORIGINAL_SIZE=$(stat -f%z "$wasm" 2>/dev/null || stat -c%s "$wasm")
ORIGINAL_KB=$((ORIGINAL_SIZE / 1024))
# Create backup
cp "$wasm" "${wasm}.bak"
# Optimize (try different levels)
# -Oz: optimize for size
# -O3: optimize for speed
# -O4: optimize aggressively
wasm-opt -Oz -o "${wasm}.opt" "$wasm" 2>/dev/null || {
echo -e "${YELLOW}Warning: wasm-opt failed for $wasm${NC}"
mv "${wasm}.bak" "$wasm"
continue
}
mv "${wasm}.opt" "$wasm"
rm "${wasm}.bak"
OPTIMIZED_SIZE=$(stat -f%z "$wasm" 2>/dev/null || stat -c%s "$wasm")
OPTIMIZED_KB=$((OPTIMIZED_SIZE / 1024))
if [ $ORIGINAL_KB -gt 0 ]; then
SAVED_KB=$((ORIGINAL_KB - OPTIMIZED_KB))
PERCENT=$((SAVED_KB * 100 / ORIGINAL_KB))
echo -e " ${GREEN}${wasm}: ${ORIGINAL_KB}KB → ${OPTIMIZED_KB}KB (-${SAVED_KB}KB, -${PERCENT}%)${NC}"
fi
done
else
echo -e "${YELLOW}wasm-opt not found, skipping optimization${NC}"
echo "Install with: cargo install wasm-opt"
echo "Or: brew install binaryen"
fi
echo ""
fi
# ==========================================
# STRIP DEBUG INFO (release only)
# ==========================================
if [ "$MODE" = "release" ] && command -v wasm-strip &> /dev/null; then
echo -e "${BLUE}Stripping debug info...${NC}"
for wasm in $(find "$OUTPUT_DIR" -name "*.wasm" -type f); do
BEFORE=$(stat -f%z "$wasm" 2>/dev/null || stat -c%s "$wasm")
wasm-strip "$wasm" 2>/dev/null || true
AFTER=$(stat -f%z "$wasm" 2>/dev/null || stat -c%s "$wasm")
SAVED=$(((BEFORE - AFTER) / 1024))
if [ $SAVED -gt 0 ]; then
echo -e " ${GREEN}${wasm}: stripped ${SAVED}KB${NC}"
fi
done
echo ""
fi
# ==========================================
# REPORT
# ==========================================
echo -e "${BLUE}Build artifacts:${NC}"
echo ""
find "$OUTPUT_DIR" -type f \( -name "*.wasm" -o -name "*.js" -o -name "*.mjs" \) | while read file; do
SIZE=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file")
SIZE_KB=$((SIZE / 1024))
if [[ "$file" == *.wasm ]]; then
echo -e " ${CYAN}$file${NC} (${SIZE_KB}KB)"
else
echo -e " $file (${SIZE_KB}KB)"
fi
done
echo ""
echo -e "${GREEN}Build complete!${NC}"
echo ""
# Usage instructions
echo "Usage in Worker:"
echo ""
echo " import wasmModule from './$OUTPUT_DIR/module.wasm';"
echo " import init, { myFunction } from './$OUTPUT_DIR/module.js';"
echo ""
echo " // Initialize WASM"
echo " await init(wasmModule);"
echo ""
echo " // Use functions"
echo " const result = myFunction(input);"
echo ""
echo "Next steps:"
echo " 1. Import WASM in your Worker"
echo " 2. Test locally: npx wrangler dev"
echo " 3. Deploy: npx wrangler deploy"
"""
Python Worker Template for Cloudflare Workers
Production-ready patterns for:
- Request routing
- Cloudflare bindings (KV, D1, R2)
- Error handling
- JSON serialization
- Data processing with NumPy
Setup:
1. Create pyproject.toml (below)
2. Create wrangler.toml (below)
3. Copy this file to src/entry.py
4. Run: npx wrangler dev
"""
# ============================================
# PYPROJECT.TOML
# ============================================
"""
[project]
name = "my-python-worker"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
"""
# ============================================
# WRANGLER.TOML
# ============================================
"""
name = "my-python-worker"
main = "src/entry.py"
compatibility_date = "2024-12-01"
compatibility_flags = ["python_workers"]
[vars]
ENVIRONMENT = "development"
[[kv_namespaces]]
binding = "CACHE"
id = "xxx"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxx"
[[r2_buckets]]
binding = "STORAGE"
bucket_name = "my-bucket"
"""
# ============================================
# MAIN WORKER CODE (src/entry.py)
# ============================================
from js import Response, Headers, URL, JSON
import json
from datetime import datetime
import uuid
# Optional: NumPy for data processing
try:
import numpy as np
HAS_NUMPY = True
except ImportError:
HAS_NUMPY = False
# ============================================
# ROUTER
# ============================================
class Router:
"""Simple URL router for Python Workers"""
def __init__(self):
self.routes = {
"GET": {},
"POST": {},
"PUT": {},
"DELETE": {},
}
def get(self, path):
def decorator(func):
self.routes["GET"][path] = func
return func
return decorator
def post(self, path):
def decorator(func):
self.routes["POST"][path] = func
return func
return decorator
def put(self, path):
def decorator(func):
self.routes["PUT"][path] = func
return func
return decorator
def delete(self, path):
def decorator(func):
self.routes["DELETE"][path] = func
return func
return decorator
def match(self, method, path):
"""Match route and extract params"""
routes = self.routes.get(method, {})
for route_path, handler in routes.items():
params = self._match_path(route_path, path)
if params is not None:
return handler, params
return None, {}
def _match_path(self, pattern, path):
"""Match path pattern with :param placeholders"""
pattern_parts = pattern.split("/")
path_parts = path.split("/")
if len(pattern_parts) != len(path_parts):
return None
params = {}
for pp, pathp in zip(pattern_parts, path_parts):
if pp.startswith(":"):
params[pp[1:]] = pathp
elif pp != pathp:
return None
return params
router = Router()
# ============================================
# HELPER FUNCTIONS
# ============================================
def json_response(data, status=200):
"""Create JSON response"""
headers = Headers.new({"Content-Type": "application/json"}.items())
body = json.dumps(data)
return Response.new(body, status=status, headers=headers)
def error_response(message, status=400):
"""Create error response"""
return json_response({
"success": False,
"error": message
}, status=status)
def success_response(data=None, status=200):
"""Create success response"""
return json_response({
"success": True,
"data": data
}, status=status)
async def get_json_body(request):
"""Parse JSON request body"""
try:
text = await request.text()
return json.loads(text)
except Exception:
return None
def get_query_params(request):
"""Parse query parameters"""
url = URL.new(request.url)
params = {}
for entry in url.searchParams.entries():
params[entry[0]] = entry[1]
return params
# ============================================
# ROUTE HANDLERS
# ============================================
@router.get("/")
async def handle_index(request, env, params):
"""Index route"""
return Response.new("Python Worker API v1.0")
@router.get("/health")
async def handle_health(request, env, params):
"""Health check"""
return json_response({
"status": "healthy",
"timestamp": datetime.utcnow().isoformat(),
"numpy_available": HAS_NUMPY
})
# ============================================
# USER CRUD HANDLERS
# ============================================
@router.get("/api/users")
async def handle_list_users(request, env, params):
"""List users with pagination"""
query = get_query_params(request)
page = int(query.get("page", 1))
limit = min(int(query.get("limit", 10)), 100)
offset = (page - 1) * limit
db = env.DB
# Get users
result = await db.prepare(
"SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?"
).bind(limit, offset).all()
# Get total count
count_result = await db.prepare(
"SELECT COUNT(*) as count FROM users"
).first()
total = count_result["count"] if count_result else 0
return json_response({
"data": result.results,
"page": page,
"limit": limit,
"total": total
})
@router.post("/api/users")
async def handle_create_user(request, env, params):
"""Create a new user"""
body = await get_json_body(request)
if not body:
return error_response("Invalid JSON body", 400)
# Validate
name = body.get("name", "").strip()
email = body.get("email", "").strip().lower()
if not name:
return error_response("Name is required", 400)
if not email or "@" not in email:
return error_response("Valid email is required", 400)
db = env.DB
# Check for existing email
existing = await db.prepare(
"SELECT id FROM users WHERE email = ?"
).bind(email).first()
if existing:
return error_response("Email already exists", 409)
# Create user
user_id = str(uuid.uuid4())
now = datetime.utcnow().isoformat()
await db.prepare(
"INSERT INTO users (id, name, email, created_at) VALUES (?, ?, ?, ?)"
).bind(user_id, name, email, now).run()
user = {
"id": user_id,
"name": name,
"email": email,
"created_at": now
}
return success_response(user, 201)
@router.get("/api/users/:id")
async def handle_get_user(request, env, params):
"""Get user by ID"""
user_id = params["id"]
db = env.DB
user = await db.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(user_id).first()
if not user:
return error_response("User not found", 404)
return success_response(user)
@router.put("/api/users/:id")
async def handle_update_user(request, env, params):
"""Update user"""
user_id = params["id"]
db = env.DB
# Check if user exists
user = await db.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(user_id).first()
if not user:
return error_response("User not found", 404)
body = await get_json_body(request)
if not body:
return error_response("Invalid JSON body", 400)
# Build update
updates = []
values = []
if "name" in body:
name = body["name"].strip()
if not name:
return error_response("Name cannot be empty", 400)
updates.append("name = ?")
values.append(name)
user["name"] = name
if "email" in body:
email = body["email"].strip().lower()
if "@" not in email:
return error_response("Invalid email", 400)
updates.append("email = ?")
values.append(email)
user["email"] = email
if not updates:
return success_response(user)
values.append(user_id)
await db.prepare(
f"UPDATE users SET {', '.join(updates)} WHERE id = ?"
).bind(*values).run()
return success_response(user)
@router.delete("/api/users/:id")
async def handle_delete_user(request, env, params):
"""Delete user"""
user_id = params["id"]
db = env.DB
result = await db.prepare(
"DELETE FROM users WHERE id = ?"
).bind(user_id).run()
if result.meta.changes == 0:
return error_response("User not found", 404)
return success_response()
# ============================================
# KV CACHE HANDLERS
# ============================================
@router.get("/api/cached/:key")
async def handle_cache_get(request, env, params):
"""Get cached value"""
key = params["key"]
kv = env.CACHE
value = await kv.get(key)
if value is None:
return error_response("Not found", 404)
return Response.new(value)
@router.put("/api/cached/:key")
async def handle_cache_set(request, env, params):
"""Set cached value with 1 hour TTL"""
key = params["key"]
kv = env.CACHE
body = await request.text()
await kv.put(key, body, expirationTtl=3600)
return Response.new("Cached")
# ============================================
# R2 STORAGE HANDLERS
# ============================================
@router.get("/api/files/:key")
async def handle_file_get(request, env, params):
"""Get file from R2"""
key = params["key"]
bucket = env.STORAGE
obj = await bucket.get(key)
if not obj:
return error_response("Not found", 404)
body = await obj.arrayBuffer()
content_type = obj.httpMetadata.contentType or "application/octet-stream"
headers = Headers.new({"Content-Type": content_type}.items())
return Response.new(body, headers=headers)
@router.put("/api/files/:key")
async def handle_file_upload(request, env, params):
"""Upload file to R2"""
key = params["key"]
bucket = env.STORAGE
content_type = request.headers.get("Content-Type") or "application/octet-stream"
body = await request.arrayBuffer()
await bucket.put(key, body, httpMetadata={"contentType": content_type})
return Response.new("Uploaded")
# ============================================
# DATA PROCESSING (NumPy)
# ============================================
@router.post("/api/compute/statistics")
async def handle_statistics(request, env, params):
"""Compute statistics on numeric data"""
if not HAS_NUMPY:
return error_response("NumPy not available", 500)
body = await get_json_body(request)
if not body:
return error_response("Invalid JSON body", 400)
data = body.get("data", [])
if not data or not isinstance(data, list):
return error_response("Data array is required", 400)
try:
arr = np.array(data, dtype=np.float64)
stats = {
"count": len(arr),
"sum": float(np.sum(arr)),
"mean": float(np.mean(arr)),
"std": float(np.std(arr)),
"min": float(np.min(arr)),
"max": float(np.max(arr)),
"median": float(np.median(arr)),
"percentiles": {
"25": float(np.percentile(arr, 25)),
"50": float(np.percentile(arr, 50)),
"75": float(np.percentile(arr, 75)),
"90": float(np.percentile(arr, 90)),
"99": float(np.percentile(arr, 99)),
}
}
return success_response(stats)
except Exception as e:
return error_response(f"Computation error: {str(e)}", 400)
@router.post("/api/compute/matrix")
async def handle_matrix(request, env, params):
"""Matrix operations"""
if not HAS_NUMPY:
return error_response("NumPy not available", 500)
body = await get_json_body(request)
if not body:
return error_response("Invalid JSON body", 400)
operation = body.get("operation")
try:
if operation == "multiply":
a = np.array(body["a"])
b = np.array(body["b"])
result = np.dot(a, b).tolist()
elif operation == "add":
a = np.array(body["a"])
b = np.array(body["b"])
result = (a + b).tolist()
elif operation == "inverse":
a = np.array(body["a"])
result = np.linalg.inv(a).tolist()
elif operation == "determinant":
a = np.array(body["a"])
result = float(np.linalg.det(a))
elif operation == "eigenvalues":
a = np.array(body["a"])
eigenvalues, _ = np.linalg.eig(a)
result = eigenvalues.tolist()
else:
return error_response(f"Unknown operation: {operation}", 400)
return success_response({"result": result, "operation": operation})
except Exception as e:
return error_response(f"Matrix error: {str(e)}", 400)
@router.post("/api/compute/normalize")
async def handle_normalize(request, env, params):
"""Normalize data to 0-1 range"""
if not HAS_NUMPY:
return error_response("NumPy not available", 500)
body = await get_json_body(request)
if not body:
return error_response("Invalid JSON body", 400)
data = body.get("data", [])
if not data:
return error_response("Data array is required", 400)
try:
arr = np.array(data, dtype=np.float64)
min_val = np.min(arr)
max_val = np.max(arr)
if max_val == min_val:
normalized = np.zeros_like(arr).tolist()
else:
normalized = ((arr - min_val) / (max_val - min_val)).tolist()
return success_response({
"normalized": normalized,
"original_min": float(min_val),
"original_max": float(max_val)
})
except Exception as e:
return error_response(f"Normalization error: {str(e)}", 400)
# ============================================
# ML INFERENCE (Simple Example)
# ============================================
class SimpleLinearModel:
"""Simple linear regression model"""
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
def predict(self, features):
"""Make prediction"""
if HAS_NUMPY:
return float(np.dot(self.weights, features) + self.bias)
else:
return sum(w * f for w, f in zip(self.weights, features)) + self.bias
# Pre-trained model (would typically load from KV)
MODEL = SimpleLinearModel([0.5, 0.3, 0.2], 0.1)
@router.post("/api/predict")
async def handle_predict(request, env, params):
"""Run ML prediction"""
body = await get_json_body(request)
if not body:
return error_response("Invalid JSON body", 400)
features = body.get("features", [])
if len(features) != 3:
return error_response("Expected 3 features", 400)
try:
prediction = MODEL.predict(features)
return success_response({
"prediction": prediction,
"features": features
})
except Exception as e:
return error_response(f"Prediction error: {str(e)}", 400)
# ============================================
# MAIN ENTRY POINT
# ============================================
async def on_fetch(request, env):
"""Main fetch handler"""
try:
url = URL.new(request.url)
path = url.pathname
method = request.method
# Match route
handler, params = router.match(method, path)
if handler:
return await handler(request, env, params)
# 404 Not Found
return error_response(f"Route not found: {method} {path}", 404)
except Exception as e:
# 500 Internal Server Error
print(f"Error: {e}")
return error_response("Internal server error", 500)
/**
* Rust Worker Template for Cloudflare Workers
*
* Production-ready patterns for:
* - Request routing
* - Cloudflare bindings (KV, D1, R2)
* - Error handling
* - JSON serialization
*
* Setup:
* 1. cargo install wasm-pack worker-build
* 2. Copy Cargo.toml configuration below
* 3. Copy this file to src/lib.rs
* 4. Configure wrangler.jsonc
* 5. Run: npx wrangler dev
*/
// ============================================
// CARGO.TOML
// ============================================
/*
[package]
name = "my-rust-worker"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
worker = "0.3"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
console_error_panic_hook = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
futures = "0.3"
uuid = { version = "1.0", features = ["v4", "js"] }
chrono = { version = "0.4", features = ["wasmbind"] }
[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
panic = "abort"
strip = true
*/
// ============================================
// WRANGLER.JSONC
// ============================================
/*
{
"name": "my-rust-worker",
"main": "build/worker/shim.mjs",
"compatibility_date": "2024-12-01",
"build": {
"command": "cargo install -q worker-build && worker-build --release"
},
"kv_namespaces": [
{ "binding": "CACHE", "id": "xxx" }
],
"d1_databases": [
{ "binding": "DB", "database_name": "my-db", "database_id": "xxx" }
],
"r2_buckets": [
{ "binding": "STORAGE", "bucket_name": "my-bucket" }
]
}
*/
// ============================================
// MAIN WORKER CODE (src/lib.rs)
// ============================================
use serde::{Deserialize, Serialize};
use worker::*;
// ============================================
// TYPE DEFINITIONS
// ============================================
#[derive(Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
}
#[derive(Deserialize)]
struct UpdateUserRequest {
name: Option<String>,
email: Option<String>,
}
#[derive(Serialize, Deserialize)]
struct User {
id: String,
name: String,
email: String,
created_at: String,
}
#[derive(Serialize)]
struct ApiResponse<T> {
success: bool,
data: Option<T>,
error: Option<String>,
}
#[derive(Serialize)]
struct PaginatedResponse<T> {
data: Vec<T>,
page: u32,
limit: u32,
total: u32,
}
// ============================================
// MAIN ENTRY POINT
// ============================================
#[event(fetch)]
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
// Set up panic hook for debugging
console_error_panic_hook::set_once();
// Router with all routes
Router::new()
// Health check
.get("/health", handle_health)
// User CRUD
.get("/api/users", handle_list_users)
.post("/api/users", handle_create_user)
.get("/api/users/:id", handle_get_user)
.put("/api/users/:id", handle_update_user)
.delete("/api/users/:id", handle_delete_user)
// Cache example
.get("/api/cached/:key", handle_cache_get)
.put("/api/cached/:key", handle_cache_set)
// Storage example
.get("/api/files/:key", handle_file_get)
.put("/api/files/:key", handle_file_upload)
// CPU-intensive
.post("/api/compute", handle_compute)
// Default
.get("/", handle_index)
.run(req, env)
.await
}
// ============================================
// ROUTE HANDLERS
// ============================================
async fn handle_index(_req: Request, _ctx: RouteContext<()>) -> Result<Response> {
Response::ok("Rust Worker API v1.0")
}
async fn handle_health(_req: Request, _ctx: RouteContext<()>) -> Result<Response> {
Response::from_json(&serde_json::json!({
"status": "healthy",
"timestamp": chrono::Utc::now().to_rfc3339()
}))
}
// ============================================
// USER CRUD HANDLERS
// ============================================
async fn handle_list_users(req: Request, ctx: RouteContext<()>) -> Result<Response> {
let url = req.url()?;
let query: std::collections::HashMap<_, _> = url.query_pairs().collect();
let page: u32 = query
.get("page")
.and_then(|p| p.parse().ok())
.unwrap_or(1);
let limit: u32 = query
.get("limit")
.and_then(|l| l.parse().ok())
.unwrap_or(10)
.min(100);
let offset = (page - 1) * limit;
let db = ctx.env.d1("DB")?;
// Get users with pagination
let users = db
.prepare("SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?")
.bind(&[limit.into(), offset.into()])?
.all()
.await?
.results::<User>()?;
// Get total count
let count: u32 = db
.prepare("SELECT COUNT(*) as count FROM users")
.first::<serde_json::Value>(None)
.await?
.and_then(|v| v.get("count").and_then(|c| c.as_u64()))
.unwrap_or(0) as u32;
let response = PaginatedResponse {
data: users,
page,
limit,
total: count,
};
Response::from_json(&response)
}
async fn handle_create_user(mut req: Request, ctx: RouteContext<()>) -> Result<Response> {
// Parse body
let input: CreateUserRequest = match req.json().await {
Ok(data) => data,
Err(_) => {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("Invalid JSON body".to_string()),
})
.map(|r| r.with_status(400));
}
};
// Validate
if input.name.trim().is_empty() {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("Name is required".to_string()),
})
.map(|r| r.with_status(400));
}
if !input.email.contains('@') {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("Invalid email".to_string()),
})
.map(|r| r.with_status(400));
}
let db = ctx.env.d1("DB")?;
// Check for existing email
let existing = db
.prepare("SELECT id FROM users WHERE email = ?")
.bind(&[input.email.to_lowercase().into()])?
.first::<serde_json::Value>(None)
.await?;
if existing.is_some() {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("Email already exists".to_string()),
})
.map(|r| r.with_status(409));
}
// Create user
let id = uuid::Uuid::new_v4().to_string();
let now = chrono::Utc::now().to_rfc3339();
db.prepare("INSERT INTO users (id, name, email, created_at) VALUES (?, ?, ?, ?)")
.bind(&[
id.clone().into(),
input.name.trim().into(),
input.email.to_lowercase().into(),
now.clone().into(),
])?
.run()
.await?;
let user = User {
id,
name: input.name.trim().to_string(),
email: input.email.to_lowercase(),
created_at: now,
};
Response::from_json(&ApiResponse {
success: true,
data: Some(user),
error: None,
})
.map(|r| r.with_status(201))
}
async fn handle_get_user(_req: Request, ctx: RouteContext<()>) -> Result<Response> {
let id = ctx.param("id").unwrap();
let db = ctx.env.d1("DB")?;
let user = db
.prepare("SELECT * FROM users WHERE id = ?")
.bind(&[id.into()])?
.first::<User>(None)
.await?;
match user {
Some(user) => Response::from_json(&ApiResponse {
success: true,
data: Some(user),
error: None,
}),
None => Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("User not found".to_string()),
})
.map(|r| r.with_status(404)),
}
}
async fn handle_update_user(mut req: Request, ctx: RouteContext<()>) -> Result<Response> {
let id = ctx.param("id").unwrap();
let db = ctx.env.d1("DB")?;
// Check if user exists
let existing = db
.prepare("SELECT * FROM users WHERE id = ?")
.bind(&[id.into()])?
.first::<User>(None)
.await?;
let mut user = match existing {
Some(u) => u,
None => {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("User not found".to_string()),
})
.map(|r| r.with_status(404));
}
};
// Parse update data
let input: UpdateUserRequest = match req.json().await {
Ok(data) => data,
Err(_) => {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("Invalid JSON body".to_string()),
})
.map(|r| r.with_status(400));
}
};
// Apply updates
if let Some(name) = input.name {
if name.trim().is_empty() {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("Name cannot be empty".to_string()),
})
.map(|r| r.with_status(400));
}
user.name = name.trim().to_string();
}
if let Some(email) = input.email {
if !email.contains('@') {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("Invalid email".to_string()),
})
.map(|r| r.with_status(400));
}
user.email = email.to_lowercase();
}
// Update in database
db.prepare("UPDATE users SET name = ?, email = ? WHERE id = ?")
.bind(&[user.name.clone().into(), user.email.clone().into(), id.into()])?
.run()
.await?;
Response::from_json(&ApiResponse {
success: true,
data: Some(user),
error: None,
})
}
async fn handle_delete_user(_req: Request, ctx: RouteContext<()>) -> Result<Response> {
let id = ctx.param("id").unwrap();
let db = ctx.env.d1("DB")?;
let result = db
.prepare("DELETE FROM users WHERE id = ?")
.bind(&[id.into()])?
.run()
.await?;
if result.meta().map(|m| m.changes).unwrap_or(0) == 0 {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("User not found".to_string()),
})
.map(|r| r.with_status(404));
}
Response::from_json(&ApiResponse::<()> {
success: true,
data: None,
error: None,
})
}
// ============================================
// KV CACHE HANDLERS
// ============================================
async fn handle_cache_get(_req: Request, ctx: RouteContext<()>) -> Result<Response> {
let key = ctx.param("key").unwrap();
let kv = ctx.kv("CACHE")?;
let value = kv.get(key).text().await?;
match value {
Some(v) => Response::ok(v),
None => Response::error("Not found", 404),
}
}
async fn handle_cache_set(mut req: Request, ctx: RouteContext<()>) -> Result<Response> {
let key = ctx.param("key").unwrap();
let kv = ctx.kv("CACHE")?;
let body = req.text().await?;
// Set with 1 hour expiration
kv.put(key, body)?
.expiration_ttl(3600)
.execute()
.await?;
Response::ok("Cached")
}
// ============================================
// R2 STORAGE HANDLERS
// ============================================
async fn handle_file_get(_req: Request, ctx: RouteContext<()>) -> Result<Response> {
let key = ctx.param("key").unwrap();
let bucket = ctx.bucket("STORAGE")?;
let object = bucket.get(key).execute().await?;
match object {
Some(obj) => {
let body = obj.body().unwrap();
let bytes = body.bytes().await?;
let content_type = obj
.http_metadata()
.content_type
.unwrap_or("application/octet-stream".to_string());
let mut headers = Headers::new();
headers.set("Content-Type", &content_type)?;
Ok(Response::from_bytes(bytes)?.with_headers(headers))
}
None => Response::error("Not found", 404),
}
}
async fn handle_file_upload(mut req: Request, ctx: RouteContext<()>) -> Result<Response> {
let key = ctx.param("key").unwrap();
let bucket = ctx.bucket("STORAGE")?;
let content_type = req
.headers()
.get("Content-Type")?
.unwrap_or("application/octet-stream".to_string());
let bytes = req.bytes().await?;
bucket
.put(key, bytes)
.http_metadata(worker::HttpMetadata {
content_type: Some(content_type),
..Default::default()
})
.execute()
.await?;
Response::ok("Uploaded")
}
// ============================================
// CPU-INTENSIVE COMPUTATION
// ============================================
#[derive(Deserialize)]
struct ComputeRequest {
data: Vec<f64>,
operation: String,
}
#[derive(Serialize)]
struct ComputeResult {
result: f64,
operation: String,
count: usize,
}
async fn handle_compute(mut req: Request, _ctx: RouteContext<()>) -> Result<Response> {
let input: ComputeRequest = match req.json().await {
Ok(data) => data,
Err(_) => {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("Invalid JSON".to_string()),
})
.map(|r| r.with_status(400));
}
};
if input.data.is_empty() {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some("Data array is empty".to_string()),
})
.map(|r| r.with_status(400));
}
let result = match input.operation.as_str() {
"sum" => input.data.iter().sum(),
"mean" => input.data.iter().sum::<f64>() / input.data.len() as f64,
"max" => input.data.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
"min" => input.data.iter().cloned().fold(f64::INFINITY, f64::min),
"std" => {
let mean = input.data.iter().sum::<f64>() / input.data.len() as f64;
let variance = input
.data
.iter()
.map(|x| (x - mean).powi(2))
.sum::<f64>()
/ input.data.len() as f64;
variance.sqrt()
}
_ => {
return Response::from_json(&ApiResponse::<()> {
success: false,
data: None,
error: Some(format!("Unknown operation: {}", input.operation)),
})
.map(|r| r.with_status(400));
}
};
Response::from_json(&ApiResponse {
success: true,
data: Some(ComputeResult {
result,
operation: input.operation,
count: input.data.len(),
}),
error: None,
})
}
// ============================================
// TESTS
// ============================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_sum() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let sum: f64 = data.iter().sum();
assert_eq!(sum, 15.0);
}
#[test]
fn test_compute_mean() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let mean = data.iter().sum::<f64>() / data.len() as f64;
assert_eq!(mean, 3.0);
}
#[test]
fn test_email_validation() {
assert!("test@example.com".contains('@'));
assert!(!"invalid".contains('@'));
}
}
/**
* WASM Integration Template for Cloudflare Workers
*
* Production-ready patterns for:
* - Loading and caching WASM modules
* - Efficient data transfer (strings, arrays, objects)
* - Memory management
* - Error handling
*
* Usage:
* 1. Build WASM module (Rust, AssemblyScript, etc.)
* 2. Copy this file to src/index.ts
* 3. Configure wrangler.jsonc
* 4. Run: bun run dev
*/
// ============================================
// WRANGLER.JSONC
// ============================================
/*
{
"name": "wasm-worker",
"main": "src/index.ts",
"compatibility_date": "2024-12-01",
"rules": [
{
"type": "CompiledWasm",
"globs": ["**/*.wasm"],
"fallthrough": true
}
]
}
*/
// ============================================
// TYPE DEFINITIONS
// ============================================
// Import WASM module
import wasmModule from './lib.wasm';
// WASM exports interface
interface WasmExports {
// Memory
memory: WebAssembly.Memory;
// Memory management
alloc: (size: number) => number;
dealloc: (ptr: number, size: number) => void;
// Numeric operations
add: (a: number, b: number) => number;
multiply: (a: number, b: number) => number;
sqrt: (n: number) => number;
// Array operations
sum_array: (ptr: number, len: number) => number;
compute_statistics: (inputPtr: number, len: number, outputPtr: number) => void;
normalize_array: (inputPtr: number, outputPtr: number, len: number) => void;
// String operations
process_string: (ptr: number, len: number) => number;
get_result_len: () => number;
// Complex operations
compute_hash: (ptr: number, len: number) => number;
process_json: (ptr: number, len: number) => number;
}
// Cloudflare environment
interface Env {
DB: D1Database;
CACHE: KVNamespace;
}
// Statistics result
interface Statistics {
count: number;
sum: number;
mean: number;
std: number;
min: number;
max: number;
}
// ============================================
// WASM MANAGER
// ============================================
class WasmManager {
private instance: WebAssembly.Instance | null = null;
private exports: WasmExports | null = null;
private encoder = new TextEncoder();
private decoder = new TextDecoder();
async initialize(): Promise<void> {
if (this.instance) return;
const response = new Response(wasmModule, {
headers: { 'Content-Type': 'application/wasm' },
});
const { instance } = await WebAssembly.instantiateStreaming(response, {
env: {
// Import functions WASM can call
abort: (msg: number, file: number, line: number, col: number) => {
console.error(`WASM abort at ${file}:${line}:${col}`);
throw new Error('WASM abort');
},
log_str: (ptr: number, len: number) => {
console.log(this.readString(ptr, len));
},
log_num: (n: number) => {
console.log('WASM log:', n);
},
},
});
this.instance = instance;
this.exports = instance.exports as WasmExports;
}
get wasm(): WasmExports {
if (!this.exports) {
throw new Error('WASM not initialized');
}
return this.exports;
}
// ==========================================
// MEMORY HELPERS
// ==========================================
readString(ptr: number, len: number): string {
const view = new Uint8Array(this.wasm.memory.buffer, ptr, len);
return this.decoder.decode(view);
}
writeString(str: string): { ptr: number; len: number } {
const bytes = this.encoder.encode(str);
const ptr = this.wasm.alloc(bytes.length);
const view = new Uint8Array(this.wasm.memory.buffer, ptr, bytes.length);
view.set(bytes);
return { ptr, len: bytes.length };
}
freeString(ptr: number, len: number): void {
this.wasm.dealloc(ptr, len);
}
readFloat64Array(ptr: number, len: number): Float64Array {
return new Float64Array(this.wasm.memory.buffer, ptr, len);
}
writeFloat64Array(arr: Float64Array): { ptr: number; len: number } {
const byteLength = arr.byteLength;
const ptr = this.wasm.alloc(byteLength);
const view = new Float64Array(this.wasm.memory.buffer, ptr, arr.length);
view.set(arr);
return { ptr, len: arr.length };
}
freeFloat64Array(ptr: number, len: number): void {
this.wasm.dealloc(ptr, len * 8);
}
// ==========================================
// NUMERIC OPERATIONS
// ==========================================
add(a: number, b: number): number {
return this.wasm.add(a, b);
}
multiply(a: number, b: number): number {
return this.wasm.multiply(a, b);
}
sqrt(n: number): number {
return this.wasm.sqrt(n);
}
// ==========================================
// ARRAY OPERATIONS
// ==========================================
sumArray(data: number[]): number {
const arr = new Float64Array(data);
const { ptr, len } = this.writeFloat64Array(arr);
const result = this.wasm.sum_array(ptr, len);
this.freeFloat64Array(ptr, len);
return result;
}
computeStatistics(data: number[]): Statistics {
const input = new Float64Array(data);
const { ptr: inputPtr, len } = this.writeFloat64Array(input);
// Allocate output buffer (6 values: count, sum, mean, std, min, max)
const outputLen = 6;
const outputPtr = this.wasm.alloc(outputLen * 8);
this.wasm.compute_statistics(inputPtr, len, outputPtr);
const output = this.readFloat64Array(outputPtr, outputLen);
// Free memory
this.freeFloat64Array(inputPtr, len);
this.wasm.dealloc(outputPtr, outputLen * 8);
return {
count: output[0],
sum: output[1],
mean: output[2],
std: output[3],
min: output[4],
max: output[5],
};
}
normalizeArray(data: number[]): number[] {
const input = new Float64Array(data);
const { ptr: inputPtr, len } = this.writeFloat64Array(input);
const outputPtr = this.wasm.alloc(len * 8);
this.wasm.normalize_array(inputPtr, outputPtr, len);
const output = Array.from(this.readFloat64Array(outputPtr, len));
// Free memory
this.freeFloat64Array(inputPtr, len);
this.wasm.dealloc(outputPtr, len * 8);
return output;
}
// ==========================================
// STRING OPERATIONS
// ==========================================
processString(input: string): string {
const { ptr: inputPtr, len: inputLen } = this.writeString(input);
const resultPtr = this.wasm.process_string(inputPtr, inputLen);
const resultLen = this.wasm.get_result_len();
const result = this.readString(resultPtr, resultLen);
// Free memory
this.freeString(inputPtr, inputLen);
this.freeString(resultPtr, resultLen);
return result;
}
// ==========================================
// COMPLEX OPERATIONS
// ==========================================
computeHash(data: Uint8Array): string {
const ptr = this.wasm.alloc(data.length);
const view = new Uint8Array(this.wasm.memory.buffer, ptr, data.length);
view.set(data);
const resultPtr = this.wasm.compute_hash(ptr, data.length);
const resultLen = 64; // SHA-256 hex string
const hash = this.readString(resultPtr, resultLen);
this.wasm.dealloc(ptr, data.length);
this.freeString(resultPtr, resultLen);
return hash;
}
processJson<T>(input: unknown): T {
const jsonStr = JSON.stringify(input);
const { ptr: inputPtr, len: inputLen } = this.writeString(jsonStr);
const resultPtr = this.wasm.process_json(inputPtr, inputLen);
const resultLen = this.wasm.get_result_len();
const resultStr = this.readString(resultPtr, resultLen);
this.freeString(inputPtr, inputLen);
this.freeString(resultPtr, resultLen);
return JSON.parse(resultStr);
}
}
// Global WASM manager
const wasmManager = new WasmManager();
// ============================================
// WORKER HANDLERS
// ============================================
async function handleNumeric(request: Request): Promise<Response> {
const { operation, a, b } = await request.json<{
operation: 'add' | 'multiply' | 'sqrt';
a: number;
b?: number;
}>();
let result: number;
switch (operation) {
case 'add':
result = wasmManager.add(a, b!);
break;
case 'multiply':
result = wasmManager.multiply(a, b!);
break;
case 'sqrt':
result = wasmManager.sqrt(a);
break;
default:
return Response.json({ error: `Unknown operation: ${operation}` }, { status: 400 });
}
return Response.json({ result, operation });
}
async function handleStatistics(request: Request): Promise<Response> {
const { data } = await request.json<{ data: number[] }>();
if (!data || !Array.isArray(data) || data.length === 0) {
return Response.json({ error: 'Data array is required' }, { status: 400 });
}
const statistics = wasmManager.computeStatistics(data);
return Response.json(statistics);
}
async function handleNormalize(request: Request): Promise<Response> {
const { data } = await request.json<{ data: number[] }>();
if (!data || !Array.isArray(data) || data.length === 0) {
return Response.json({ error: 'Data array is required' }, { status: 400 });
}
const normalized = wasmManager.normalizeArray(data);
return Response.json({ normalized });
}
async function handleHash(request: Request): Promise<Response> {
const body = await request.text();
const data = new TextEncoder().encode(body);
const hash = wasmManager.computeHash(data);
return Response.json({ hash });
}
async function handleProcess(request: Request): Promise<Response> {
const { input } = await request.json<{ input: string }>();
if (!input) {
return Response.json({ error: 'Input string is required' }, { status: 400 });
}
const result = wasmManager.processString(input);
return Response.json({ result });
}
// ============================================
// BATCH PROCESSOR (Efficient for large datasets)
// ============================================
class BatchProcessor {
private inputBuffer: number;
private outputBuffer: number;
private bufferSize: number;
constructor(maxSize: number = 10000) {
const wasm = wasmManager.wasm;
this.bufferSize = maxSize;
this.inputBuffer = wasm.alloc(maxSize * 8);
this.outputBuffer = wasm.alloc(maxSize * 8);
}
process(data: Float64Array): Float64Array {
if (data.length > this.bufferSize) {
throw new Error(`Data exceeds buffer size: ${data.length} > ${this.bufferSize}`);
}
const wasm = wasmManager.wasm;
// Copy to pre-allocated buffer
const input = new Float64Array(wasm.memory.buffer, this.inputBuffer, data.length);
input.set(data);
// Process (normalize in this example)
wasm.normalize_array(this.inputBuffer, this.outputBuffer, data.length);
// Return view (zero-copy read)
return new Float64Array(wasm.memory.buffer, this.outputBuffer, data.length);
}
dispose(): void {
const wasm = wasmManager.wasm;
wasm.dealloc(this.inputBuffer, this.bufferSize * 8);
wasm.dealloc(this.outputBuffer, this.bufferSize * 8);
}
}
// ============================================
// MAIN EXPORT
// ============================================
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// Initialize WASM on first request
await wasmManager.initialize();
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;
try {
// Route handlers
if (method === 'GET' && path === '/') {
return new Response('WASM Worker API v1.0');
}
if (method === 'GET' && path === '/health') {
return Response.json({
status: 'healthy',
wasm: 'initialized',
});
}
if (method === 'POST' && path === '/api/compute/numeric') {
return handleNumeric(request);
}
if (method === 'POST' && path === '/api/compute/statistics') {
return handleStatistics(request);
}
if (method === 'POST' && path === '/api/compute/normalize') {
return handleNormalize(request);
}
if (method === 'POST' && path === '/api/compute/hash') {
return handleHash(request);
}
if (method === 'POST' && path === '/api/process') {
return handleProcess(request);
}
// Batch processing endpoint
if (method === 'POST' && path === '/api/batch/normalize') {
const { data } = await request.json<{ data: number[] }>();
if (!data || data.length === 0) {
return Response.json({ error: 'Data required' }, { status: 400 });
}
const processor = new BatchProcessor(data.length);
try {
const input = new Float64Array(data);
const result = processor.process(input);
return Response.json({ normalized: Array.from(result) });
} finally {
processor.dispose();
}
}
return Response.json({ error: 'Not found' }, { status: 404 });
} catch (error) {
console.error('WASM error:', error);
if (error instanceof WebAssembly.RuntimeError) {
return Response.json(
{ error: 'WASM runtime error', message: error.message },
{ status: 500 }
);
}
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
},
};
// ============================================
// EXAMPLE RUST WASM SOURCE
// ============================================
/*
// src/lib.rs - Corresponding Rust code for WASM exports
use wasm_bindgen::prelude::*;
use std::alloc::{alloc, dealloc, Layout};
#[wasm_bindgen]
pub fn alloc(size: usize) -> *mut u8 {
let layout = Layout::from_size_align(size, 8).unwrap();
unsafe { alloc(layout) }
}
#[wasm_bindgen]
pub fn dealloc_mem(ptr: *mut u8, size: usize) {
let layout = Layout::from_size_align(size, 8).unwrap();
unsafe { dealloc(ptr, layout) }
}
#[wasm_bindgen]
pub fn add(a: f64, b: f64) -> f64 {
a + b
}
#[wasm_bindgen]
pub fn multiply(a: f64, b: f64) -> f64 {
a * b
}
#[wasm_bindgen]
pub fn sqrt(n: f64) -> f64 {
n.sqrt()
}
#[wasm_bindgen]
pub fn sum_array(ptr: *const f64, len: usize) -> f64 {
let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
slice.iter().sum()
}
#[wasm_bindgen]
pub fn compute_statistics(input_ptr: *const f64, len: usize, output_ptr: *mut f64) {
let input = unsafe { std::slice::from_raw_parts(input_ptr, len) };
let output = unsafe { std::slice::from_raw_parts_mut(output_ptr, 6) };
let sum: f64 = input.iter().sum();
let mean = sum / len as f64;
let variance = input.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / len as f64;
let std = variance.sqrt();
let min = input.iter().cloned().fold(f64::INFINITY, f64::min);
let max = input.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
output[0] = len as f64;
output[1] = sum;
output[2] = mean;
output[3] = std;
output[4] = min;
output[5] = max;
}
#[wasm_bindgen]
pub fn normalize_array(input_ptr: *const f64, output_ptr: *mut f64, len: usize) {
let input = unsafe { std::slice::from_raw_parts(input_ptr, len) };
let output = unsafe { std::slice::from_raw_parts_mut(output_ptr, len) };
let min = input.iter().cloned().fold(f64::INFINITY, f64::min);
let max = input.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let range = max - min;
for (i, &val) in input.iter().enumerate() {
output[i] = if range == 0.0 { 0.0 } else { (val - min) / range };
}
}
*/