> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript

> Run node/js inside the workspace on quickjs, a sandboxed JavaScript engine for small scripts and pipe transforms.

Shell lines like `node script.mjs` or `js -e "..."` need a JavaScript
engine. Mirage calls that engine a **runtime**, and `node` and `js` are
two names for the same command family. One runtime ships today:

| Runtime | Engine | Filesystem | Default |
| - | - | - | - |
| `quickjs` | [quickjs-ng](https://github.com/quickjs-ng/quickjs) compiled to WebAssembly, run in-process on [wasmtime](https://wasmtime.dev) | Workspace mounts only, bridged through Mirage; no host filesystem, no network | Yes |

## What it is for

`node`/`js` runs **small, self-contained scripts and pipe transforms**:
compute, JSON munging, regex text processing, code generation. It is a
bare modern engine (ES2023 syntax, ES modules, `JSON`, `Promise`,
top-level await), **not** Node: there is no `require`, no npm, no
`process`, no `fs`, no `fetch`. Anything that needs those belongs in a
sandboxed deployment, not the workspace runtime.

```bash theme={null}
js -e "console.log(6 * 7)"                 # inline
node /data/transform.mjs arg1 arg2         # a mounted script, args in scriptArgs
cat data.json | node -e "const d = JSON.parse(std.in.readAsString()); console.log(d.length)"
```

Three input forms, mirroring `python3`:

* **`-e <code>`** evaluates the argument.
* **A file** (`node script.js`) is read through the workspace before the
  run, so a mounted script executes.
* **stdin** with no `-e` or file runs the piped text as the program.

`scriptArgs` holds the arguments after the code or script, and
`std.in.readAsString()` reads piped stdin (the quickjs-ng `std`/`os`
globals are exposed). Output follows the real engine: `console.log` and
the `print` global append a newline, `std.out.puts`/`std.err.puts`
write raw, `std.out.printf`/`std.err.printf` C-format and return the characters written, and there is no `console.error` (write stderr with
`std.err.puts`). These match the TypeScript `quickjs` runtime, so a
script behaves the same in both languages.

Relative paths start at the Mirage session's working directory. Direct
`run()` calls can override it with `RunArgs.cwd`; one-shot `eval()` calls
inherit the current workspace directory. `os.getcwd()` returns
`[directory, 0]`, and `os.chdir(path)` returns `0` or negative WASI errno.
A guest directory change lasts for that execution and leaves the Mirage
session and host process directories unchanged.

## Modules

A `.mjs` file runs as an ES module automatically (top-level
`import`/`export`/`await`); `.js` and `-e` run as a classic script. Pass
`-m`/`--module` to force module mode for inline code:

```bash theme={null}
js -m -e "const x = await Promise.resolve(41); console.log(x + 1)"
```

## Isolation

The engine runs under WebAssembly capability isolation: it sees only what
the run passes it. Host files and the network are invisible. Workspace
mounts are visible with no setup: mirage intercepts the sandbox's
filesystem calls and bridges them through the workspace dispatch, so
`std.open('/data/f.txt', 'r')` reads the mount live, and `os.stat`/`os.mkdir`/`os.rename`/`os.remove`/`os.utimes` stat and mutate it (0 or -errno in WASI numbering, like the real engine) — RAM, Redis, S3, or
a virtual backend — and writes are immediately visible to every command,
with the same cache, write modes, and per-session mount narrowing as
`cat`. A read-only mount (or a session narrowed to read) fails the open,
so `std.open` returns `null` instead of writing. The engine's `os` module
has no extended-attribute calls; run `getfattr` or `setfattr` in the shell
instead.

This is capability isolation, not a VFS sandbox: the sandboxed code
cannot reach your files or the network, but its CPU and memory are bounded
only by `command_limits` timeouts. For untrusted code or hard VFS
limits, run behind a sandboxed deployment.

Each run gets its own epoch-interruption engine, so a `command_limits`
timeout traps the run and reclaims the thread instead of leaking it.

## Setup

The runtime needs the `quickjs` extra plus a WASI build of quickjs-ng
(the `qjs-wasi.wasm` asset from a
[quickjs-ng release](https://github.com/quickjs-ng/quickjs/releases)):

```bash theme={null}
pip install mirage-ai[quickjs]
```

Point mirage at the directory holding `qjs-wasi.wasm` with the `home`
option on the runtime entry (or the MIRAGE\_QUICKJS\_HOME environment
variable):

```python theme={null}
from mirage.runtime.js import QuickJsRuntime

ws = Workspace({"/data": RAMVFS()},
               mode=MountMode.EXEC,
               runtimes=[QuickJsRuntime(config={"home": "/path/to/qjs-dir"}), "workspace"])
```

```yaml theme={null}
runtimes:
  - name: quickjs
    config:
      home: /path/to/qjs-dir
  - workspace
```

The first run compiles `qjs-wasi.wasm` and caches the compilation as
`qjs-wasi.cwasm` next to it; runs after that boot in milliseconds.

## Resource limits

`node`/`js` is a command like any other: the same `command_limits`
that guard `cat` or `python3` guard it, enforced at the same central
point. A run that exceeds `timeout_seconds` answers with exit 124, and
`max_bytes`/`max_lines` cap its output. Firing the guard also cancels
the run and reclaims the engine at the deadline.

## Route policy

The quickjs runtime carries the evaluator capability, so a JS-only
world has a [route policy](/home/route-policy): `route_policy: ./policy.js`
evaluates JavaScript per line with `ctx` bound as a global, and the
completion value (the last expression) is the verdict.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.