JavaScript Runtime
The IOTA SDK includes a powerful JavaScript runtime based on Goja for executing user-defined scripts with access to database, services, and HTTP capabilities.
Overview
The JavaScript Runtime enables:
- Scheduled Scripts: Run periodic tasks with cron expressions
- One-off Scripts: Execute ad-hoc scripts on demand
- HTTP Endpoints: Create custom API endpoints with JavaScript
- Event Handlers: Respond to application events
- Data Transformations: Custom validation and transformation rules
- System Integration: Access to services, database, and external APIs
Architecture
Runtime Features
// Goja runtime provides:
// - ECMAScript 5.1 compliance
// - ES6/ES2015 features support
// - Pure Go implementation (no CGO)
// - Excellent error messages
// - Synchronous execution model
// - Resource limits (CPU time, memory)
Script Types
- Cron Jobs: Scheduled execution with cron expressions
- One-Off Scripts: Manual execution from UI or API
- HTTP Endpoints: Dynamic routes responding to HTTP requests
- Embedded Scripts: Inline scripts for validation/transformation
- Event Handlers: React to domain events
API Surface
Context Object
const context = {
tenant: { id: "uuid", name: "Tenant Name" },
user: { id: "uuid", email: "user@example.com", name: "User Name" },
script: { id: "uuid", name: "Script Name", type: "cron" },
execution: { id: "uuid", triggeredBy: "cron|manual|api" }
};
Database Access
// Tenant-scoped database queries
const result = await db.query("SELECT * FROM users WHERE id = $1", [userID]);
const user = await db.findOne("users", { email: "test@example.com" });
const users = await db.findMany("users", { status: "active" });
Service Layer
// Access to IOTA SDK services (tenant-scoped)
const clients = await services.clients.list({ limit: 100 });
const client = await services.clients.get(clientID);
const newClient = await services.clients.create({ name: "New Client" });
HTTP Client
// Make external HTTP requests
const response = await http.get("https://api.example.com/data");
const posted = await http.post("https://api.example.com/data", { key: "value" });
Storage (Key-Value Store)
// Tenant-scoped persistent storage
await storage.set("key", { data: "value" }, 3600); // TTL: 1 hour
const value = await storage.get("key");
await storage.delete("key");
Utilities
// UUID generation
const id = utils.uuid();
// Date/time helpers
const now = utils.date.now();
const formatted = utils.date.format(now, "YYYY-MM-DD");
const future = utils.date.addDays(now, 30);
// Crypto utilities
const hash = utils.crypto.hash("data", "sha256");
Usage Examples
Cron Job: Send Notifications
// Cron expression: "0 9 * * *" (daily at 9 AM)
async function main() {
console.info(`Running notification job for tenant: ${context.tenant.name}`);
// Get inactive clients (no activity in 30 days)
const thirtyDaysAgo = utils.date.addDays(utils.date.now(), -30);
const clients = await services.clients.list({
filters: [
{ field: 'last_activity', operator: 'lt', value: thirtyDaysAgo }
],
limit: 100
});
let notified = 0;
for (const client of clients) {
try {
// Send notification
await services.notifications.sendEmail({
to: client.email,
subject: "We miss you!",
body: `Hello ${client.name}, we haven't seen you in 30 days.`
});
// Log the event
await events.publish('client.notified', {
client_id: client.id,
type: 'inactivity'
});
notified++;
console.log(`Notified ${client.name}`);
} catch (error) {
console.error(`Failed to notify ${client.name}: ${error.message}`);
}
}
return {
success: true,
processed: clients.length,
notified: notified
};
}
Order Validation Rule
// Validate orders before processing
async function validateOrder(order) {
const errors = [];
// Check inventory
for (const item of order.items) {
const product = await services.products.get(item.product_id);
if (!product) {
errors.push(`Product not found: ${item.product_id}`);
continue;
}
if (product.stock < item.quantity) {
errors.push(`Insufficient stock for ${product.name}`);
}
}
// Check credit limit
const customer = await services.clients.get(order.customer_id);
if (customer.credit_limit > 0) {
const outstanding = await db.query(
"SELECT COALESCE(SUM(amount), 0) as total FROM invoices WHERE customer_id = $1 AND status != 'paid'",
[customer.id]
);
if (outstanding[0].total + order.total > customer.credit_limit) {
errors.push(`Order exceeds credit limit`);
}
}
return {
valid: errors.length === 0,
errors: errors
};
}
HTTP Endpoint: Custom Report
// Endpoint: GET /api/custom/sales-report?start=2024-01-01&end=2024-01-31
async function handleRequest(req) {
// Validate permissions
if (!context.user.permissions.includes('reports.view')) {
return {
status: 403,
body: { error: 'Access denied' }
};
}
const startDate = req.query.start || utils.date.addDays(utils.date.now(), -30);
const endDate = req.query.end || utils.date.now();
// Query sales data
const sales = await db.query(
`SELECT DATE(order_date) as date, COUNT(*) as orders, SUM(total) as revenue
FROM orders
WHERE order_date BETWEEN $1 AND $2
GROUP BY DATE(order_date)
ORDER BY date DESC`,
[startDate, endDate]
);
// Aggregate data
const summary = {
total_orders: sales.reduce((sum, day) => sum + day.orders, 0),
total_revenue: sales.reduce((sum, day) => sum + day.revenue, 0),
days_included: sales.length
};
return {
status: 200,
headers: { 'Content-Type': 'application/json' },
body: {
summary: summary,
daily: sales
}
};
}
Event Handler: Update Analytics
// Handle order.created event
async function handleOrderCreated(event) {
const order = event.data;
// Update daily sales cache
const today = utils.date.format(utils.date.now(), "YYYY-MM-DD");
const cacheKey = `daily_sales_${today}`;
const current = await storage.get(cacheKey) || { count: 0, total: 0 };
current.count += 1;
current.total += order.total;
await storage.set(cacheKey, current, 86400); // Cache for 24 hours
console.info(`Updated sales cache: ${current.count} orders, $${current.total}`);
}
Configuration
Environment Variables
# Enable JavaScript runtime
SCRIPTS_ENABLED=true
# Script execution limits
SCRIPT_TIMEOUT_SECONDS=30
SCRIPT_MAX_MEMORY_MB=128
# VM pool configuration
SCRIPT_VM_POOL_SIZE=10
SCRIPT_VM_TIMEOUT_SECONDS=30
Module Registration
// In your application setup
app.RegisterModule(scripts.NewModule())
// Permissions for script management
permissions.ScriptCreate // Create new scripts
permissions.ScriptRead // View scripts
permissions.ScriptUpdate // Modify scripts
permissions.ScriptDelete // Delete scripts
permissions.ScriptExecute // Execute scripts
Security Considerations
Sandboxing
Scripts are executed in a sandbox with:
- Disabled global scope access
- No filesystem access
- Limited to whitelisted services
- Memory and CPU time limits
- Request timeout enforcement
Tenant Isolation
All database queries and service calls are automatically scoped to the current tenant:
// All queries are tenant-scoped
const users = await db.query("SELECT * FROM users"); // Only current tenant's users
const clients = await services.clients.list(); // Only current tenant's clients
Permissions
Scripts inherit user permissions:
// User's role determines accessible services
// If user can't access reports, script can't either
if (!context.user.permissions.includes('reports.view')) {
throw new Error('Access denied');
}
Error Handling
Compilation Errors
// Syntax errors caught during compilation
try {
// Invalid syntax
const x = [;
} catch (e) {
// Returns detailed error with line/column info
console.error(`Syntax error: ${e.message}`);
}
Runtime Errors
// Runtime errors with stack traces
try {
const result = await services.clients.get(null);
} catch (e) {
console.error(`Service error: ${e.message}`);
// Returns error with context
}
Timeout Handling
// Scripts automatically timeout if execution exceeds limit
// DEFAULT: 30 seconds
// Configure via SCRIPT_TIMEOUT_SECONDS
// Catch timeout errors
try {
while (true) { } // Infinite loop
} catch (e) {
console.error("Script timeout"); // Caught at 30 seconds
}
Performance Optimization
Caching
// Cache expensive computations
const cacheKey = `expensive_calculation_${param}`;
const cached = await storage.get(cacheKey);
if (cached) {
return cached;
}
// Expensive operation
const result = await expensiveCalculation();
// Cache for 1 hour
await storage.set(cacheKey, result, 3600);
return result;
Batch Operations
// Use batch operations for efficiency
const userIDs = await db.query(
"SELECT id FROM users WHERE status = 'active' LIMIT 1000"
);
// Process in batches to avoid memory issues
for (let i = 0; i < userIDs.length; i += 100) {
const batch = userIDs.slice(i, i + 100);
await processBatch(batch);
}
Testing Scripts
Local Development
# Use SDK's script development server
make scripts dev-server
# Test scripts locally before uploading
node scripts/test-runner.js my-script.js
Unit Testing
// Test validation logic
describe('Order Validation', () => {
it('should reject orders exceeding credit limit', async () => {
const order = { customer_id: 123, total: 10000 };
const result = await validateOrder(order);
expect(result.valid).toBe(false);
});
});
Monitoring and Logging
Structured Logging
// Logs include context automatically
console.log('User action', { user_id: context.user.id, action: 'create' });
// Outputs:
// {
// "message": "User action",
// "level": "info",
// "tenant_id": "...",
// "user_id": "...",
// "user_action": "create",
// "source_file": "my-script.js"
// }
Metrics
Scripts can publish metrics:
// Track execution metrics
await events.publish('script.metric', {
script_name: context.script.name,
duration_ms: 1234,
items_processed: 500,
errors: 0
});
Limitations and Workarounds
| Limitation | Workaround |
|---|---|
| No filesystem access | Use database or object storage |
| No native modules | Use SDK-provided APIs |
| Single-threaded | Use async operations |
| Memory limit | Process in batches |
| Time limit | Optimize code or split tasks |
Examples Repository
See /docs/examples/scripts/ for more examples:
send-notifications.js- Scheduled notificationsdata-sync.js- Sync data with external servicereport-generator.js- Generate reportswebhook-processor.js- Process webhooksvalidation-rules.js- Order validation
For more information, see the Advanced Features Overview or the JavaScript Runtime Integration Spec.