Skip to content

WASM Procedures: Language SDKs

WASM Procedures: Language SDKs

Part of: WASM Procedures User Guide


Rust SDK

Setup

Cargo.toml:

[package]
name = "my_procedure"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
heliosdb-sdk = "1.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[profile.release]
opt-level = "z" # Optimize for size
lto = true # Enable link-time optimization
codegen-units = 1 # Better optimization
strip = true # Strip symbols

Basic Procedure

use heliosdb_sdk::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: i64,
name: String,
email: String,
}
/// Get user by ID from database
#[procedure]
pub fn get_user(user_id: i64) -> Result<User, String> {
let result = exec_sql(&format!("SELECT id, name, email FROM users WHERE id = {}", user_id))?;
if result.is_empty() {
return Err(format!("User {} not found", user_id));
}
let user = User {
id: result.get_i64(0, 0)?,
name: result.get_string(0, 1)?,
email: result.get_string(0, 2)?,
};
Ok(user)
}

Type-Safe Bindings

use heliosdb_sdk::*;
// Strongly typed parameters and return values
#[procedure]
pub fn process_order(order_id: i64, apply_discount: bool) -> Result<OrderResult, String> {
// Type-safe SQL execution
let order: Order = exec_sql_typed(
"SELECT * FROM orders WHERE id = ?",
&[&order_id]
)?;
// Type-safe JSON serialization
let result = OrderResult {
order_id: order.id,
total: order.total,
processed: true,
};
Ok(result)
}
#[derive(Debug, Serialize, Deserialize)]
struct Order {
id: i64,
total: f64,
status: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct OrderResult {
order_id: i64,
total: f64,
processed: bool,
}

Error Handling

use heliosdb_sdk::*;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ProcedureError {
#[error("Database error: {0}")]
Database(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Business logic error: {0}")]
BusinessLogic(String),
}
#[procedure]
pub fn transfer_funds(
from_account: i64,
to_account: i64,
amount: f64
) -> Result<(), ProcedureError> {
// Validation
if amount <= 0.0 {
return Err(ProcedureError::InvalidInput("Amount must be positive".into()));
}
// Start transaction
let tx = begin_transaction()
.map_err(|e| ProcedureError::Database(e))?;
// Check balance
let balance: f64 = exec_sql_typed(
"SELECT balance FROM accounts WHERE id = ?",
&[&from_account]
).map_err(|e| ProcedureError::Database(e))?;
if balance < amount {
rollback_transaction(tx)?;
return Err(ProcedureError::BusinessLogic("Insufficient funds".into()));
}
// Execute transfer
exec_sql(&format!("UPDATE accounts SET balance = balance - {} WHERE id = {}", amount, from_account))
.map_err(|e| ProcedureError::Database(e))?;
exec_sql(&format!("UPDATE accounts SET balance = balance + {} WHERE id = {}", amount, to_account))
.map_err(|e| ProcedureError::Database(e))?;
commit_transaction(tx)
.map_err(|e| ProcedureError::Database(e))?;
log_info(&format!("Transferred ${} from {} to {}", amount, from_account, to_account));
Ok(())
}

Testing

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_discount() {
let discount = calculate_discount(1000.0, "gold").unwrap();
assert_eq!(discount, 150.0);
}
#[test]
fn test_invalid_tier() {
let discount = calculate_discount(1000.0, "platinum").unwrap();
assert_eq!(discount, 0.0);
}
#[test]
fn test_transfer_invalid_amount() {
let result = transfer_funds(1, 2, -100.0);
assert!(result.is_err());
}
}

Build & Test:

Terminal window
# Run tests
cargo test
# Build release
cargo build --target wasm32-wasi --release
# Check binary size
ls -lh target/wasm32-wasi/release/*.wasm
# Optimize with wasm-opt
wasm-opt -Oz target/wasm32-wasi/release/my_procedure.wasm \
-o procedure_optimized.wasm

JavaScript/TypeScript SDK

Setup

package.json:

{
"name": "my-procedure",
"version": "1.0.0",
"scripts": {
"asbuild:debug": "asc assembly/index.ts --target debug",
"asbuild:release": "asc assembly/index.ts --target release --optimize",
"test": "npm run asbuild:debug && node tests/test.js"
},
"devDependencies": {
"assemblyscript": "^0.27.0",
"@heliosdb/wasm-sdk": "^1.0.0"
}
}

asconfig.json:

{
"targets": {
"debug": {
"outFile": "build/debug.wasm",
"textFile": "build/debug.wat",
"sourceMap": true,
"debug": true
},
"release": {
"outFile": "build/release.wasm",
"textFile": "build/release.wat",
"sourceMap": false,
"optimizeLevel": 3,
"shrinkLevel": 2,
"converge": true,
"noAssert": true
}
},
"options": {
"bindings": "esm"
}
}

Basic Procedure

assembly/index.ts:

import {
execSql,
beginTransaction,
commitTransaction,
rollbackTransaction,
log,
ResultSet
} from "@heliosdb/wasm-sdk";
export function getUserCount(): i32 {
const result = execSql("SELECT COUNT(*) FROM users");
return result.getInt(0, 0);
}
export function createUser(name: string, email: string): i64 {
const result = execSql(
`INSERT INTO users (name, email) VALUES ('${name}', '${email}') RETURNING id`
);
const userId = result.getInt64(0, 0);
log("info", `Created user ${userId}: ${name}`);
return userId;
}

AssemblyScript Usage

import { execSql, log } from "@heliosdb/wasm-sdk";
// Type-safe function
export function processOrder(orderId: i64, discount: f64): f64 {
// Query with type safety
const result = execSql(
`SELECT total FROM orders WHERE id = ${orderId}`
);
if (result.rowCount() === 0) {
log("error", `Order ${orderId} not found`);
return 0.0;
}
const total = result.getFloat(0, 0);
const finalTotal = total * (1.0 - discount);
// Update order
execSql(
`UPDATE orders SET final_total = ${finalTotal} WHERE id = ${orderId}`
);
return finalTotal;
}
// Working with arrays
export function calculateAveragePrice(productIds: Array<i64>): f64 {
let sum: f64 = 0.0;
let count: i32 = 0;
for (let i = 0; i < productIds.length; i++) {
const result = execSql(
`SELECT price FROM products WHERE id = ${productIds[i]}`
);
if (result.rowCount() > 0) {
sum += result.getFloat(0, 0);
count++;
}
}
return count > 0 ? sum / f64(count) : 0.0;
}

Async/Await Pattern

import { execSqlAsync, logAsync } from "@heliosdb/wasm-sdk";
export async function processOrderAsync(orderId: i64): Promise<boolean> {
await logAsync("info", `Processing order ${orderId}`);
try {
// Async database operations
const result = await execSqlAsync(
`SELECT status FROM orders WHERE id = ${orderId}`
);
if (result.rowCount() === 0) {
throw new Error("Order not found");
}
const status = result.getString(0, 0);
if (status === "pending") {
await execSqlAsync(
`UPDATE orders SET status = 'processed' WHERE id = ${orderId}`
);
return true;
}
return false;
} catch (error) {
await logAsync("error", `Failed to process order: ${error.message}`);
return false;
}
}

Testing

tests/test.js:

const fs = require('fs');
const { instantiate } = require('@heliosdb/wasm-sdk');
async function test() {
const wasmModule = fs.readFileSync('./build/release.wasm');
const instance = await instantiate(wasmModule);
// Test getUserCount
const count = instance.exports.getUserCount();
console.log('User count:', count);
// Test createUser
const userId = instance.exports.createUser('John Doe', 'john@example.com');
console.log('Created user ID:', userId);
console.log('All tests passed!');
}
test().catch(console.error);

Python SDK

Setup

requirements.txt:

heliosdb-wasm-sdk>=1.0.0
wasmtime>=15.0.0
typing-extensions>=4.8.0

Install:

Terminal window
pip install -r requirements.txt

Basic Procedure

from typing import List, Dict, Optional
import heliosdb
def get_user_count() -> int:
"""Get total number of users in the database."""
result = heliosdb.exec_sql("SELECT COUNT(*) FROM users")
return result.rows[0][0]
def create_user(name: str, email: str) -> int:
"""Create a new user and return the ID."""
result = heliosdb.exec_sql(
f"INSERT INTO users (name, email) VALUES ('{name}', '{email}') RETURNING id"
)
user_id = result.rows[0][0]
heliosdb.log_info(f"Created user {user_id}: {name}")
return user_id

Type Hints

from typing import List, Dict, Optional, TypedDict
from dataclasses import dataclass
import heliosdb
@dataclass
class User:
id: int
name: str
email: str
active: bool
class OrderSummary(TypedDict):
order_id: int
total: float
discount: float
final_total: float
def get_user(user_id: int) -> Optional[User]:
"""Get user by ID with type-safe return."""
result = heliosdb.exec_sql(
f"SELECT id, name, email, active FROM users WHERE id = {user_id}"
)
if not result.rows:
return None
row = result.rows[0]
return User(
id=row[0],
name=row[1],
email=row[2],
active=row[3]
)
def calculate_order_summary(order_id: int) -> OrderSummary:
"""Calculate order summary with discounts."""
result = heliosdb.exec_sql(
f"SELECT total, discount_rate FROM orders WHERE id = {order_id}"
)
total = result.rows[0][0]
discount_rate = result.rows[0][1]
discount = total * discount_rate
final_total = total - discount
return OrderSummary(
order_id=order_id,
total=total,
discount=discount,
final_total=final_total
)

Error Handling

import heliosdb
from typing import Optional
class ProcedureError(Exception):
"""Base exception for procedure errors."""
pass
class InsufficientFundsError(ProcedureError):
"""Raised when account has insufficient funds."""
pass
def transfer_funds(
from_account: int,
to_account: int,
amount: float
) -> None:
"""Transfer funds between accounts with proper error handling."""
if amount <= 0:
raise ValueError("Amount must be positive")
tx = None
try:
# Begin transaction
tx = heliosdb.begin_transaction()
# Check balance
result = heliosdb.exec_sql(
f"SELECT balance FROM accounts WHERE id = {from_account}"
)
if not result.rows:
raise ProcedureError(f"Account {from_account} not found")
balance = result.rows[0][0]
if balance < amount:
raise InsufficientFundsError(
f"Insufficient funds: has ${balance:.2f}, needs ${amount:.2f}"
)
# Execute transfer
heliosdb.exec_sql(
f"UPDATE accounts SET balance = balance - {amount} WHERE id = {from_account}"
)
heliosdb.exec_sql(
f"UPDATE accounts SET balance = balance + {amount} WHERE id = {to_account}"
)
# Commit transaction
heliosdb.commit_transaction(tx)
heliosdb.log_info(f"Transferred ${amount:.2f} from {from_account} to {to_account}")
except Exception as e:
if tx:
heliosdb.rollback_transaction(tx)
heliosdb.log_error(f"Transfer failed: {str(e)}")
raise

Testing

import unittest
from procedure import get_user_count, create_user, transfer_funds, InsufficientFundsError
class TestProcedures(unittest.TestCase):
def test_get_user_count(self):
"""Test getting user count."""
count = get_user_count()
self.assertIsInstance(count, int)
self.assertGreaterEqual(count, 0)
def test_create_user(self):
"""Test creating a new user."""
user_id = create_user("Test User", "test@example.com")
self.assertIsInstance(user_id, int)
self.assertGreater(user_id, 0)
def test_transfer_insufficient_funds(self):
"""Test transfer with insufficient funds."""
with self.assertRaises(InsufficientFundsError):
transfer_funds(1, 2, 1000000.0)
def test_transfer_negative_amount(self):
"""Test transfer with negative amount."""
with self.assertRaises(ValueError):
transfer_funds(1, 2, -100.0)
if __name__ == '__main__':
unittest.main()

Go SDK

Setup

go.mod:

module my-procedure
go 1.21
require (
github.com/heliosdb/heliosdb-go-sdk v1.0.0
)

Basic Procedure

package main
import (
"fmt"
"heliosdb"
)
//export getUserCount
func getUserCount() int64 {
result, err := heliosdb.ExecSQL("SELECT COUNT(*) FROM users")
if err != nil {
heliosdb.LogError(fmt.Sprintf("Failed to get user count: %v", err))
return 0
}
count, _ := result.GetInt64(0, 0)
return count
}
//export createUser
func createUser(name, email string) int64 {
query := fmt.Sprintf(
"INSERT INTO users (name, email) VALUES ('%s', '%s') RETURNING id",
name, email,
)
result, err := heliosdb.ExecSQL(query)
if err != nil {
heliosdb.LogError(fmt.Sprintf("Failed to create user: %v", err))
return 0
}
userID, _ := result.GetInt64(0, 0)
heliosdb.LogInfo(fmt.Sprintf("Created user %d: %s", userID, name))
return userID
}
func main() {
// Required for TinyGo
}

TinyGo Usage

package main
import (
"fmt"
"heliosdb"
)
// User represents a database user
type User struct {
ID int64
Name string
Email string
Active bool
}
//export getUser
func getUser(userID int64) *User {
query := fmt.Sprintf(
"SELECT id, name, email, active FROM users WHERE id = %d",
userID,
)
result, err := heliosdb.ExecSQL(query)
if err != nil {
heliosdb.LogError(fmt.Sprintf("Query failed: %v", err))
return nil
}
if result.RowCount() == 0 {
return nil
}
user := &User{
ID: result.GetInt64(0, 0),
Name: result.GetString(0, 1),
Email: result.GetString(0, 2),
Active: result.GetBool(0, 3),
}
return user
}
//export processOrderBatch
func processOrderBatch(orderIDs []int64) int32 {
successCount := int32(0)
for _, orderID := range orderIDs {
if processOrder(orderID) {
successCount++
}
}
heliosdb.LogInfo(fmt.Sprintf("Processed %d/%d orders", successCount, len(orderIDs)))
return successCount
}
func processOrder(orderID int64) bool {
query := fmt.Sprintf(
"UPDATE orders SET status = 'processed', processed_at = NOW() WHERE id = %d",
orderID,
)
_, err := heliosdb.ExecSQL(query)
return err == nil
}
func main() {}

Error Handling

package main
import (
"errors"
"fmt"
"heliosdb"
)
var (
ErrInsufficientFunds = errors.New("insufficient funds")
ErrAccountNotFound = errors.New("account not found")
ErrInvalidAmount = errors.New("invalid amount")
)
//export transferFunds
func transferFunds(fromAccount, toAccount int64, amount float64) error {
// Validation
if amount <= 0 {
heliosdb.LogWarn("Transfer attempted with invalid amount")
return ErrInvalidAmount
}
// Begin transaction
tx, err := heliosdb.BeginTransaction()
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
// Check source account balance
query := fmt.Sprintf("SELECT balance FROM accounts WHERE id = %d", fromAccount)
result, err := heliosdb.ExecSQL(query)
if err != nil {
heliosdb.RollbackTransaction(tx)
return err
}
if result.RowCount() == 0 {
heliosdb.RollbackTransaction(tx)
return ErrAccountNotFound
}
balance := result.GetFloat64(0, 0)
if balance < amount {
heliosdb.RollbackTransaction(tx)
heliosdb.LogWarn(fmt.Sprintf("Insufficient funds: has $%.2f, needs $%.2f", balance, amount))
return ErrInsufficientFunds
}
// Debit from source
_, err = heliosdb.ExecSQL(
fmt.Sprintf("UPDATE accounts SET balance = balance - %.2f WHERE id = %d", amount, fromAccount),
)
if err != nil {
heliosdb.RollbackTransaction(tx)
return fmt.Errorf("failed to debit account: %w", err)
}
// Credit to destination
_, err = heliosdb.ExecSQL(
fmt.Sprintf("UPDATE accounts SET balance = balance + %.2f WHERE id = %d", amount, toAccount),
)
if err != nil {
heliosdb.RollbackTransaction(tx)
return fmt.Errorf("failed to credit account: %w", err)
}
// Commit transaction
if err := heliosdb.CommitTransaction(tx); err != nil {
return fmt.Errorf("failed to commit: %w", err)
}
heliosdb.LogInfo(fmt.Sprintf("Transferred $%.2f from %d to %d", amount, fromAccount, toAccount))
return nil
}
func main() {}

Testing

package main
import (
"testing"
)
func TestGetUserCount(t *testing.T) {
count := getUserCount()
if count < 0 {
t.Errorf("Expected non-negative count, got %d", count)
}
}
func TestCreateUser(t *testing.T) {
userID := createUser("Test User", "test@example.com")
if userID <= 0 {
t.Error("Expected positive user ID")
}
}
func TestTransferInvalidAmount(t *testing.T) {
err := transferFunds(1, 2, -100.0)
if err != ErrInvalidAmount {
t.Errorf("Expected ErrInvalidAmount, got %v", err)
}
}
func TestTransferInsufficientFunds(t *testing.T) {
err := transferFunds(1, 2, 1000000.0)
if err != ErrInsufficientFunds {
t.Errorf("Expected ErrInsufficientFunds, got %v", err)
}
}

Build:

Terminal window
# Run tests
go test
# Build with TinyGo
tinygo build -o procedure.wasm -target wasi main.go
# Optimize
tinygo build -o procedure.wasm -target wasi -opt=z -no-debug main.go
# Check size
ls -lh procedure.wasm

Navigation: