---
title: JavaScript
description: 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
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). These match the Python `quickjs` runtime, so a
script behaves the same in both languages.

## 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
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 — 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.

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

Each run gets its own epoch-interruption engine, so a `command_safeguards`
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
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
from mirage.runtime.js import QuickJsRuntime

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

```yaml
runtimes:
  - name: quickjs
    home: /path/to/qjs-dir
  - vfs
```

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_safeguards`
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.
