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.