rune

docs · concepts

Runtimes.

Rune separates the language from the API. The host API — the surface your Rune writes against — is defined once and lives in Rust. Each language runtime is a backend that implements that contract, so 'add a language' means 'wire a new backend', not 'rewrite the platform'.

Today: TypeScript

The Node.js backend is the only runtime currently wired. It embeds libnode (V8 + Node) inside the Paper JVM by way of the Rust loader, which uses Panama FFM to keep both sides in the same address space. Your script runs in a real Node environment — fs, http, process, and the npm ecosystem are all available — but the upcalls to Bukkit don't go over a socket. They're direct synchronous calls through V8 → Rust → JVM.

// Plain Node-side work happens normally.
import fs from "node:fs";
import { z } from "zod";

const envSchema = z.object({ DATABASE_URI: z.string() });
const env = envSchema.parse(loadEnvFile());

// Bukkit work hits the JVM through FFM.
@Listener
export class Pulse {
  @EventHandler(Events.PlayerJoinEvent)
  onJoin(e: PlayerJoinEvent) {
    const player = e.getPlayer();        // live ref, not a snapshot
    rune.runOnMain(() => {                // schedule on the main thread
      player.sendMessage("welcome.");
    });
  }
}

Wasm (wired, not yet exposed)

Wasmtime is included as a second runtime backend. The plumbing works — a script declared with language = "wasm" in rune.toml routes to the Wasm backend instead of Node — but the host bindings exposed to Wasm guests are still being designed. The published Wasm interface lives in wit/ in the Rune repo and uses the Component Model. Treat this as preview surface; the WIT will move before 1.0.

Planned: Python, Lua, Rust → Wasm

The roadmap covers three more guests. Python and Lua get native embeddings (CPython and LuaJIT respectively, both via FFM, same pattern as Node). Rust gets the easier path: you compile to Wasm and the existing Wasmtime runtime hosts it. The host API is language-agnostic by design; what your Rune sees is the same across guests.

How a language is added

Each backend implements a single trait, LanguageRuntime, in crates/rune-host-api. The trait says: given a path to a script, load it; given a query callback, register it; given a reload signal, tear down and rebuild. Everything else — event dispatch, command registration, store persistence — is host-side machinery routed back to the guest via callbacks. New backends register themselves in the loader; nothing in the Paper plugin changes.