Skip to content

Getting Started

This guide walks through adding xript to an application from scratch. By the end, you will have a working sandboxed expression evaluator that users can extend safely.

The universal runtime uses QuickJS compiled to WebAssembly: it works in browsers, Node.js, Deno, and more.

Terminal window
npm install @xriptjs/runtime

Create a manifest.json describing what your application exposes to scripts. Start with a few safe bindings:

{
"$schema": "https://xript.dev/schema/manifest/v0.1.json",
"xript": "0.1",
"name": "my-app",
"version": "1.0.0",
"bindings": {
"greet": {
"description": "Returns a greeting for the given name.",
"params": [{ "name": "name", "type": "string" }],
"returns": "string"
},
"add": {
"description": "Adds two numbers.",
"params": [
{ "name": "a", "type": "number" },
{ "name": "b", "type": "number" }
],
"returns": "number"
}
}
}

Only xript and name are required. Everything else is optional and layered on as needed.

Each binding in the manifest needs a host-side implementation. These are regular JavaScript functions:

const hostBindings = {
greet: (name) => `Hello, ${name}!`,
add: (a, b) => a + b,
};

Initialize the WASM sandbox, then wire the manifest and bindings together:

import { initXript } from "@xriptjs/runtime";
const xript = await initXript();
const runtime = xript.createRuntime(manifest, {
hostBindings,
console: { log: console.log, warn: console.warn, error: console.error },
});

initXript() loads the QuickJS WASM module once. After that, createRuntime() is synchronous: create as many runtimes as you need.

Now you can safely evaluate user expressions:

runtime.execute('greet("World")'); // { value: "Hello, World!", duration_ms: ... }
runtime.execute('add(2, 3)'); // { value: 5, duration_ms: ... }
runtime.execute('add(1, add(2, 3))'); // { value: 6, duration_ms: ... }

Scripts can compose your bindings with standard JavaScript:

runtime.execute('[1, 2, 3].map(n => add(n, 10))'); // { value: [11, 12, 13], ... }

When you’re done with a runtime, free its WASM resources:

runtime.dispose();

Anything not declared in the manifest is inaccessible:

runtime.execute('process.exit(1)'); // Error: process is not defined
runtime.execute('require("fs")'); // Error: require is not defined
runtime.execute('eval("1 + 1")'); // Error: eval() is not permitted
runtime.execute('fetch("https://x")'); // Error: fetch is not defined

The sandbox guarantees that user scripts cannot escape the boundaries you define.

  • Scaffold a new project with npx @xriptjs/init. See Init CLI.
  • Add capabilities to gate sensitive operations. See the Capabilities spec.
  • Add namespaces to organize related bindings. See the Manifest spec.
  • Generate TypeScript definitions from your manifest with xript-typegen. See Type Generator.
  • Generate documentation from your manifest with xript-docgen. See Doc Generator.
  • Use the Node.js runtime for file-based workflows and native V8 performance. See Node.js Runtime.
  • Explore the runtime API in depth. See JS/WASM Runtime.
  • Run the full example in the repository: examples/expression-evaluator/.