> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/trailbaseio/trailbase/llms.txt
> Use this file to discover all available pages before exploring further.

# WebAssembly Runtime

> Using WebAssembly for custom endpoints and background jobs in TrailBase

TrailBase includes a WebAssembly runtime powered by [Wasmtime](https://wasmtime.dev/), enabling you to extend your backend with custom TypeScript/JavaScript endpoints and scheduled jobs without recompiling the server.

## Overview

The WASM runtime provides:

* **Custom HTTP endpoints** - Create API routes with TypeScript/JavaScript
* **Background jobs** - Schedule periodic tasks (minutely, hourly, daily, weekly)
* **Database access** - Query your database from WASM code
* **Hot reload** - Update code without restarting TrailBase
* **Sandboxed execution** - Secure isolation from the host system
* **Multiple isolates** - Parallel request handling

## Quick Start

### 1. Install WASM Component

TrailBase provides official TypeScript/JavaScript support:

```bash theme={null}
# List available components
trail components list

# Install TypeScript runtime
trail components add trailbase-guest-ts
```

### 2. Create Your First Endpoint

Create `traildepot/wasm/index.ts`:

```typescript traildepot/wasm/index.ts theme={null}
import { defineConfig } from "trailbase-wasm";
import { HttpHandler, HttpRequest, HttpResponse } from "trailbase-wasm/http";

export default defineConfig({
  httpHandlers: [
    HttpHandler.get("/hello", (req: HttpRequest) => {
      return "Hello from WASM!";
    }),
    
    HttpHandler.get("/greet/:name", (req: HttpRequest) => {
      const name = req.getPathParam("name");
      return `Hello, ${name}!`;
    }),
    
    HttpHandler.post("/json", (req: HttpRequest) => {
      const data = req.json();
      return HttpResponse.json({
        received: data,
        timestamp: Date.now(),
      });
    }),
  ],
});
```

### 3. Build and Deploy

Compile your TypeScript to WASM:

```bash theme={null}
# Build WASM component
cd traildepot/wasm
npm install
npm run build

# Output: dist/component.wasm
```

### 4. Test Your Endpoint

Restart TrailBase to load the WASM component:

```bash theme={null}
trail run
```

Test your endpoint:

```bash theme={null}
curl http://localhost:4000/hello
# Output: Hello from WASM!

curl http://localhost:4000/greet/Alice
# Output: Hello, Alice!

curl -X POST http://localhost:4000/json -d '{"message":"test"}'
# Output: {"received":{"message":"test"},"timestamp":1234567890}
```

## HTTP Handlers

### Request Methods

```typescript theme={null}
import { defineConfig } from "trailbase-wasm";
import { HttpHandler, HttpRequest } from "trailbase-wasm/http";

export default defineConfig({
  httpHandlers: [
    HttpHandler.get("/users", listUsers),
    HttpHandler.post("/users", createUser),
    HttpHandler.put("/users/:id", updateUser),
    HttpHandler.patch("/users/:id", patchUser),
    HttpHandler.delete("/users/:id", deleteUser),
  ],
});

function listUsers(req: HttpRequest) {
  return HttpResponse.json({ users: [] });
}

function createUser(req: HttpRequest) {
  const data = req.json();
  return HttpResponse.json({ id: 1, ...data });
}

function updateUser(req: HttpRequest) {
  const id = req.getPathParam("id");
  const data = req.json();
  return HttpResponse.json({ id, ...data });
}

function patchUser(req: HttpRequest) {
  const id = req.getPathParam("id");
  const data = req.json();
  return HttpResponse.json({ id, updated: data });
}

function deleteUser(req: HttpRequest) {
  const id = req.getPathParam("id");
  return HttpResponse.json({ deleted: id });
}
```

### Request API

Access request data:

```typescript theme={null}
HttpHandler.get("/api/example", (req: HttpRequest) => {
  // Query parameters
  const page = req.getQueryParam("page") ?? "1";
  const limit = req.getQueryParam("limit") ?? "10";
  
  // Path parameters
  const id = req.getPathParam("id");
  
  // Request body as JSON
  const body = req.json();
  
  // Raw request body
  const raw = req.body();
  
  return HttpResponse.json({
    page,
    limit,
    id,
    body,
  });
});
```

### Response API

```typescript theme={null}
import { HttpResponse } from "trailbase-wasm/http";

// Return plain text (default)
HttpHandler.get("/text", () => {
  return "Hello, World!";
});

// Return JSON
HttpHandler.get("/json", () => {
  return HttpResponse.json({ status: "ok" });
});

// Custom status code
HttpHandler.get("/not-found", () => {
  return HttpResponse.json(
    { error: "Not found" },
    { status: 404 }
  );
});

// Custom headers
HttpHandler.get("/custom", () => {
  return HttpResponse.json(
    { data: "test" },
    { 
      headers: {
        "X-Custom-Header": "value",
        "Cache-Control": "max-age=3600",
      }
    }
  );
});
```

## Database Access

Query your database from WASM:

```typescript theme={null}
import { defineConfig } from "trailbase-wasm";
import { HttpHandler } from "trailbase-wasm/http";
import { query } from "trailbase-wasm/db";

export default defineConfig({
  httpHandlers: [
    HttpHandler.get("/stats/todos", async () => {
      // Execute SQL query
      const rows = await query(
        "SELECT COUNT(*) as total, SUM(completed) as done FROM todos",
        []
      );
      
      return HttpResponse.json({
        total: rows[0][0],
        done: rows[0][1],
        pending: rows[0][0] - rows[0][1],
      });
    }),
    
    HttpHandler.get("/todos/search", async (req) => {
      const q = req.getQueryParam("q") ?? "";
      
      // Parameterized queries prevent SQL injection
      const rows = await query(
        "SELECT * FROM todos WHERE text LIKE ? LIMIT 10",
        [`%${q}%`]
      );
      
      return HttpResponse.json({ results: rows });
    }),
  ],
});
```

<Warning>
  Always use parameterized queries to prevent SQL injection:

  **Bad:**

  ```typescript theme={null}
  const rows = await query(`SELECT * FROM todos WHERE id = ${id}`);
  ```

  **Good:**

  ```typescript theme={null}
  const rows = await query("SELECT * FROM todos WHERE id = ?", [id]);
  ```
</Warning>

### Complex Queries

```typescript theme={null}
HttpHandler.get("/reports/user-activity", async () => {
  const rows = await query(`
    SELECT 
      u.email,
      COUNT(t.id) as todo_count,
      SUM(t.completed) as completed_count
    FROM _user u
    LEFT JOIN todos t ON t.user = u.id
    GROUP BY u.id
    ORDER BY todo_count DESC
    LIMIT 10
  `, []);
  
  return HttpResponse.json({ topUsers: rows });
});
```

## Background Jobs

Schedule periodic tasks:

```typescript theme={null}
import { defineConfig } from "trailbase-wasm";
import { JobHandler } from "trailbase-wasm/job";
import { query } from "trailbase-wasm/db";

export default defineConfig({
  jobHandlers: [
    // Run every minute
    JobHandler.minutely("cleanup-old-todos", async () => {
      const weekAgo = Math.floor(Date.now() / 1000) - (7 * 24 * 60 * 60);
      
      await query(
        "DELETE FROM todos WHERE completed = 1 AND updated_at < ?",
        [weekAgo]
      );
      
      console.log("Cleaned up old completed todos");
    }),
    
    // Run every hour
    JobHandler.hourly("send-reminders", async () => {
      const rows = await query(`
        SELECT t.*, u.email 
        FROM todos t
        JOIN _user u ON t.user = u.id
        WHERE t.completed = 0 AND t.due_date < ?
      `, [Math.floor(Date.now() / 1000)]);
      
      for (const row of rows) {
        console.log(`Reminder: ${row[2]} is overdue for ${row[4]}`);
        // Send email notification
      }
    }),
    
    // Run daily at midnight
    JobHandler.daily("daily-report", async () => {
      const rows = await query(
        "SELECT COUNT(*) FROM todos WHERE created_at > ?",
        [Math.floor(Date.now() / 1000) - (24 * 60 * 60)]
      );
      
      console.log(`Daily report: ${rows[0][0]} todos created today`);
    }),
    
    // Run weekly (Sunday at midnight)
    JobHandler.weekly("weekly-cleanup", async () => {
      console.log("Weekly cleanup started");
      // Perform weekly maintenance tasks
    }),
  ],
});
```

## Async Operations

### Timers

```typescript theme={null}
import { addPeriodicCallback } from "trailbase-wasm";

HttpHandler.get("/delayed-response", async () => {
  // Sleep
  await new Promise(resolve => setTimeout(resolve, 1000));
  return "Delayed by 1 second";
});

HttpHandler.get("/periodic", () => {
  let count = 0;
  
  // Schedule periodic callback
  addPeriodicCallback(250, (cancel) => {
    console.log(`Callback #${count}`);
    count++;
    
    if (count >= 10) {
      cancel(); // Stop after 10 iterations
    }
  });
  
  return "Started periodic task";
});
```

### Parallel Queries

```typescript theme={null}
HttpHandler.get("/dashboard", async () => {
  // Execute queries in parallel
  const [users, todos, articles] = await Promise.all([
    query("SELECT COUNT(*) FROM _user", []),
    query("SELECT COUNT(*) FROM todos", []),
    query("SELECT COUNT(*) FROM articles", []),
  ]);
  
  return HttpResponse.json({
    users: users[0][0],
    todos: todos[0][0],
    articles: articles[0][0],
  });
});
```

## Real-World Examples

### Vector Search Endpoint

From the [Coffee Vector Search](https://github.com/trailbaseio/trailbase/tree/main/examples/coffee-vector-search) example:

```typescript theme={null}
import { defineConfig } from "trailbase-wasm";
import { HttpHandler, HttpRequest, HttpResponse } from "trailbase-wasm/http";
import { query } from "trailbase-wasm/db";

export default defineConfig({
  httpHandlers: [
    HttpHandler.get("/api/search", async (req: HttpRequest) => {
      const q = req.getQueryParam("q");
      const limit = parseInt(req.getQueryParam("limit") ?? "10");
      
      if (!q) {
        return HttpResponse.json({ error: "Missing query" }, { status: 400 });
      }
      
      // Perform vector similarity search
      const results = await query(`
        SELECT 
          c.name,
          c.description,
          c.flavor_notes,
          vec_distance_cosine(c.embedding, vec_f32(?)) as distance
        FROM coffees c
        ORDER BY distance ASC
        LIMIT ?
      `, [await embed(q), limit]);
      
      return HttpResponse.json({ results });
    }),
  ],
});

// Mock embedding function
async function embed(text: string): Promise<Float32Array> {
  // In production, call an embedding API
  return new Float32Array(384); // Example: 384-dimensional embedding
}
```

### API Rate Limiting

```typescript theme={null}
const rateLimits = new Map<string, number[]>();

function rateLimit(ip: string, maxRequests: number, windowMs: number): boolean {
  const now = Date.now();
  const requests = rateLimits.get(ip) ?? [];
  
  // Remove old requests outside the window
  const recentRequests = requests.filter(time => now - time < windowMs);
  
  if (recentRequests.length >= maxRequests) {
    return false; // Rate limit exceeded
  }
  
  recentRequests.push(now);
  rateLimits.set(ip, recentRequests);
  return true;
}

HttpHandler.get("/api/expensive", (req) => {
  const ip = req.getHeader("x-forwarded-for") ?? "unknown";
  
  // Allow 10 requests per minute
  if (!rateLimit(ip, 10, 60000)) {
    return HttpResponse.json(
      { error: "Rate limit exceeded" },
      { status: 429 }
    );
  }
  
  // Handle request...
  return "OK";
});
```

### Data Aggregation

```typescript theme={null}
HttpHandler.get("/api/analytics/overview", async () => {
  const [totalUsers, activeUsers, totalTodos, completionRate] = await Promise.all([
    query("SELECT COUNT(*) FROM _user", []),
    query("SELECT COUNT(DISTINCT user) FROM todos WHERE updated_at > ?", [
      Math.floor(Date.now() / 1000) - (7 * 24 * 60 * 60),
    ]),
    query("SELECT COUNT(*) FROM todos", []),
    query("SELECT AVG(completed) FROM todos", []),
  ]);
  
  return HttpResponse.json({
    users: {
      total: totalUsers[0][0],
      active: activeUsers[0][0],
    },
    todos: {
      total: totalTodos[0][0],
      completionRate: (completionRate[0][0] * 100).toFixed(1) + "%",
    },
  });
});
```

## Hot Reload

During development, enable hot reload to update code without restarting:

```typescript traildepot/wasm/hot-reload.ts theme={null}
import { watch } from "fs";
import { exec } from "child_process";

watch("./src", { recursive: true }, (eventType, filename) => {
  console.log(`File changed: ${filename}`);
  
  // Rebuild WASM component
  exec("npm run build", (error, stdout, stderr) => {
    if (error) {
      console.error(`Build failed: ${stderr}`);
      return;
    }
    
    console.log("WASM component rebuilt successfully");
  });
});
```

Run during development:

```bash theme={null}
cd traildepot/wasm
npm run dev
```

<Info>
  TrailBase automatically reloads WASM components when the file changes.
</Info>

## Configuration

### Runtime Settings

Configure the WASM runtime in `traildepot/config.textproto`:

```protobuf theme={null}
server {
  # Number of JavaScript isolates (default: number of CPUs)
  runtime_threads: 4
}
```

### Filesystem Access

Grant sandboxed filesystem access:

```bash theme={null}
# Start with filesystem root
trail run --runtime-root-fs /path/to/data
```

Access files from WASM:

```typescript theme={null}
HttpHandler.get("/files/read", async () => {
  // Read from sandboxed root
  const content = await readFile("/data/config.json");
  return content;
});
```

## Component Management

### List Available Components

```bash theme={null}
trail components list
```

Output:

```
Available first-party components:
- trailbase-guest-ts (TypeScript/JavaScript runtime)
- trailbase-guest-rust (Rust runtime)
```

### Install Component

```bash theme={null}
# Install from registry
trail components add trailbase-guest-ts

# Install from file
trail components add ./component.wasm

# Install from URL
trail components add https://example.com/component.wasm
```

### List Installed Components

```bash theme={null}
trail components installed
```

### Remove Component

```bash theme={null}
trail components remove trailbase-guest-ts
```

### Update Components

```bash theme={null}
# Update all installed first-party components
trail components update
```

## Debugging

### Console Logging

```typescript theme={null}
console.log("Info message");
console.warn("Warning message");
console.error("Error message");
```

Logs appear in TrailBase's output:

```
[WASM] Info message
[WASM] Warning message
[WASM] Error message
```

### Error Handling

```typescript theme={null}
HttpHandler.get("/api/risky", async (req) => {
  try {
    const data = await query("SELECT * FROM nonexistent_table", []);
    return HttpResponse.json(data);
  } catch (error) {
    console.error("Query failed:", error);
    return HttpResponse.json(
      { error: "Internal server error" },
      { status: 500 }
    );
  }
});
```

### Performance Monitoring

```typescript theme={null}
HttpHandler.get("/api/slow", async () => {
  const start = Date.now();
  
  const result = await query("SELECT * FROM large_table", []);
  
  const duration = Date.now() - start;
  console.log(`Query took ${duration}ms`);
  
  return HttpResponse.json({ duration, rows: result.length });
});
```

## Best Practices

<Steps>
  <Step title="Keep Handlers Lightweight">
    WASM has overhead for each invocation. For simple operations, use Record APIs directly.

    **Use WASM for:**

    * Complex business logic
    * Custom data transformations
    * Third-party API integrations
    * Scheduled background tasks

    **Avoid WASM for:**

    * Simple CRUD operations (use Record APIs)
    * Static content (use `--public-dir`)
  </Step>

  <Step title="Use Parameterized Queries">
    Always use parameterized queries to prevent SQL injection:

    ```typescript theme={null}
    // Good
    await query("SELECT * FROM todos WHERE user = ?", [userId]);

    // Bad
    await query(`SELECT * FROM todos WHERE user = ${userId}`);
    ```
  </Step>

  <Step title="Handle Errors Gracefully">
    Catch errors and return meaningful HTTP responses:

    ```typescript theme={null}
    HttpHandler.post("/api/resource", async (req) => {
      try {
        const data = req.json();
        // Process...
        return HttpResponse.json({ success: true });
      } catch (error) {
        console.error("Error:", error);
        return HttpResponse.json(
          { error: String(error) },
          { status: 400 }
        );
      }
    });
    ```
  </Step>

  <Step title="Optimize Database Queries">
    Use indexes and limit result sets:

    ```typescript theme={null}
    // Create index in migration
    // CREATE INDEX _todos__user_index ON todos(user);

    // Use LIMIT in queries
    await query(
      "SELECT * FROM todos WHERE user = ? ORDER BY created_at DESC LIMIT 100",
      [userId]
    );
    ```
  </Step>
</Steps>

## Next Steps

<CardGroup cols={2}>
  <Card title="First App" icon="rocket" href="/guides/first-app">
    Build your first application
  </Card>

  <Card title="Database Setup" icon="database" href="/guides/database-setup">
    Design efficient schemas
  </Card>

  <Card title="Realtime Subscriptions" icon="bolt" href="/guides/realtime-subscriptions">
    Combine WASM with live updates
  </Card>

  <Card title="CLI Usage" icon="terminal" href="/guides/cli-usage">
    Manage WASM components
  </Card>
</CardGroup>

## Examples

* [WASM Guest TypeScript](https://github.com/trailbaseio/trailbase/tree/main/examples/wasm-guest-ts) - Basic TypeScript endpoints
* [Coffee Vector Search](https://github.com/trailbaseio/trailbase/tree/main/examples/coffee-vector-search) - Vector similarity search with embeddings
* [WASM Guest Rust](https://github.com/trailbaseio/trailbase/tree/main/examples/wasm-guest-rust) - Rust-based WASM components
