# Elide for Deno users

> What works unchanged, which commands and permission flags change, and how Elide compares with Deno.

Elide runs TypeScript, Web APIs, `node:*` modules and npm packages; the `Deno` global is absent.

## Run the same code

```bash
elide app.ts                    # or: elide run app.ts
elide install --ecosystems=npm  # dependencies from package.json
elide test                      # node:test files
elide serve server.ts           # a module that default-exports { fetch }
```

- TypeScript runs with no compile step and no `tsconfig.json`.
- `fetch`, `Request`, `Response`, Streams, `URL` and Web Crypto are globals.
- `node:fs`, `node:http`, `node:worker_threads` and most other built-ins load with or without the `node:` prefix.
- A module that default-exports `{ fetch }`, as `deno serve` expects, serves under `elide serve` and `elide run`.
- `Elide.serve({ port, fetch })` takes the same Fetch-style handler as `Deno.serve`.

Start with [JavaScript and TypeScript](https://elide.help/docs/web). Module status is in the [API Overview](https://elide.help/docs/api-overview); suite
results are in the [Compatibility Matrix](https://elide.help/docs/compatibility).

## What changes

| Deno | Elide |
| --- | --- |
| `deno run app.ts` | `elide app.ts` |
| `npm:`, `jsr:` and `https:` imports | Bare specifiers resolved from `node_modules` |
| `deno add`, `deno install` | Add to `package.json`, then `elide install --ecosystems=npm` |
| `deno.lock` | `aube-lock.yaml` |
| `deno task build` | `elide run build`; reads `package.json` scripts, not `deno.json` tasks |
| `deno test`, `Deno.test` | `elide test` with `node:test` and `node:assert` |
| `deno run --inspect app.ts` | `elide run --debugger=cdp app.ts` |
| `deno run --watch app.ts` | No equivalent |
| `--env-file` | Call `process.loadEnvFile()` in code |
| `Deno.serve({ fetch })` | `Elide.serve({ fetch })` |
| `Deno.readTextFile`, `Deno.Command` | `node:fs` and `node:child_process` |
| Web `Worker` | `node:worker_threads` |
| `deno fmt`, `deno lint`, `deno compile` | No equivalent for JavaScript or TypeScript |

## Benchmarks

Startup: `hyperfine -N -w 10` on a 24-core Linux workstation, running a hello-world script.

| Runtime | Command | Mean |
| --- | --- | --- |
| Deno | `deno run hello.js` | 6.1 ± 0.3 ms |
| Elide | `elide run hello.js` | 2.1 ± 0.1 ms |
| Bun | `bun hello.js` | 1.8 ± 0.2 ms |
| Node | `node hello.js` | 11.4 ± 0.8 ms |

Bun starts faster than Elide.

Serving: `wrk -t12 -c256 -d10s` against a hello-world server. Each server ran on the same 24 CPUs and `wrk` on 24
others.

| Runtime | Requests/s | Mean latency |
| --- | --- | --- |
| Deno | 280,666 | 0.89 ms |
| Elide | 600,352 | 419 µs |
| Bun | 147,742 | 1.70 ms |
| Node | 91,464 | 3.22 ms |

Every server ran with its defaults: Deno, Bun and Node serve on one thread, Elide on several. This compares
defaults, not tuned deployments.

## Strengths

- Starts a hello-world script in 2.1 ms against Deno's 6.1 ms.
- Served 600,352 requests per second against Deno's 280,666, both with default settings.
- `elide serve --contexts=auto server.ts` runs several copies of the app, sized from the CPU cores, on Linux.
- Limits workers, open files, heap and run time, and logs each filesystem decision; see the security model below.
- The same binary runs WebAssembly, compiles and runs Java and Kotlin, and runs Python after `elide setup python`.

## Weaknesses

- Everything is allowed until you pass a flag, and no flag denies all network or subprocess access.
- No `Deno` global, `npm:` or `jsr:` imports, URL imports, watch mode, formatter or linter for TypeScript.
- Node-API compatibility is 86.8% and WinterTC 95.9% on their test suites.
- `node:http2`, `node:cluster`, `node:inspector` and `node:wasi` throw `ERR_UNKNOWN_BUILTIN_MODULE`.
- `https.createServer` throws `ERR_HTTPS_SERVER_UNSUPPORTED`, and there is no `WebSocket` global.
- No macOS Intel build; macOS releases are Apple Silicon only.

## Security model

Deno denies access until an `--allow-*` flag grants it. Elide allows access until a flag restricts it.

| Deno | Elide |
| --- | --- |
| No flags: everything denied | No flags: everything allowed |
| `-A`, `--allow-all` | No flags |
| `--allow-read[=PATHS]` | `--allow-read[=PATHS]`; denies all other filesystem access |
| `--allow-write[=PATHS]` | `--allow-write[=PATHS]`; denies all other filesystem access |
| `--allow-net[=HOSTS]` | `--allow-net[=HOSTS]` |
| `--allow-run[=CMDS]` | `--allow-run[=CMDS]` |
| `--deny-net=HOSTS`, `--deny-run=CMDS` | `--deny-net=HOSTS`, `--deny-run=CMDS` |
| `--deny-read`, `--deny-write` | No equivalent; grant only the paths you need |
| `--allow-env[=NAMES]` | No equivalent; `process.env` is always readable, and `--allow-env` scopes `elide.pkl` |
| `--allow-ffi` | Allowed; `--no-native` denies native access |
| Prompt for a missing permission | No prompt; the call fails and names the flag to add |

Elide is more permissive in three places:

- `--sandbox` restricts the filesystem only. Network and subprocesses stay open until `--allow-net=HOSTS`,
  `--allow-run=CMDS` or a `--deny-*` flag scopes them.
- A bare `--allow-net` or `--allow-run`, or an empty value, allows everything.
- Programs read every environment variable through `process.env`.

Elide adds these controls to the permission flags:

| Flag | Effect |
| --- | --- |
| `--fs-audit` | Log each filesystem decision to stderr. |
| `--limit=workers=N`, `--limit=fds=N` | Limit live workers and open `node:fs` handles. |
| `--max-mem=SIZE` | Limit the heap size. |
| `--timeout=DURATION` | Stop the run after `DURATION` with exit `124`. |

A Deno command and the closest Elide command:

```bash
deno run --allow-read=. --allow-net=api.example.com app.ts
elide --allow-read=. --allow-net=api.example.com --allow-run=git --timeout=30s app.ts
```

`--sandbox=trusted` is the only available profile. See [Sandbox and Limits](https://elide.help/docs/sandbox).

## Next steps

- [Installation](https://elide.help/docs/installation)
- [JavaScript and TypeScript](https://elide.help/docs/web)
- [Sandbox and Limits](https://elide.help/docs/sandbox)
