# Chapter 17: JavaScript Host Interop

> ⚠ **Planned — not in Loup 1.25.** This chapter describes the design in the Language Reference (§2-3.1 `extern js`, §2-3.2 `@jsexport`, §2-3.3 the `lang.interop.js` facades). The toolchain does not implement it yet, so its listings are design sketches and are not verified.

**What you'll learn:** how scripts actually run today and what that means for JavaScript interop; how to check which capabilities a script was granted, which works now; and the planned design for calling JavaScript from Loup (`extern js`), exposing Loup to JavaScript (`@jsexport`), and the typed `lang.interop.js` facades.

## Scripts Do Not Run on a JavaScript Host

Earlier editions of this book said that scripts run on Node, Bun, Deno or in a browser. That is not what ships. In Loup 1.25, `loup run` type-checks a script and runs it on an interpreter built into the `loup` binary, as an ordinary process on Linux, macOS or Windows (Chapter 1). There is no JavaScript engine underneath, no `--host` option, and no JavaScript globals to call.

The interop features are accordingly absent:

- `extern js func parseInt(s: string, radix: int): int;` is accepted by the parser, but a call to it fails to compile: ``type of 'parseInt' is not yet known`` and ``'parseInt' is not callable``.
- `@jsexport` on a function is accepted and ignored; the function is an ordinary Loup function.
- `from lang.interop.js import (fetch);` fails: `'lang.interop.js' is not a standard-library module`.
- A WebAssembly build for the browser (`loup-bootstrap build --target browser`) produces a bare `.wasm` file with no JavaScript loader around it; see [The Loup Web Book](../loup-web/README.md).

For the things scripts usually wanted JavaScript for, use the standard library: files and processes through the prelude (`readText`, `writeText`, `exec`) and `io.fs`, `io.path` and `io.proc`; JSON through `system.json`; sockets through `net.tcp`; an HTTP server through `net.http`. (An HTTP *client* is planned; `net.http` is server-only in 1.25.)

## What Works Today: Checking Capabilities

Every script runs with a set of capabilities granted by `loup run` (Chapter 1, "Permissions"). A script can ask which ones it has, which lets it fail early with a useful message instead of halfway through its work:

```loup
@mode(host.scripted)

let caps = runtime.capabilities();
println(i"granted: {$ caps $}");

for (needed in ["fs", "net"]) {
    if (runtime.has(needed)) {
        println(i"{$ needed $}: yes");
    } else {
        println(i"{$ needed $}: no (run with --allow-{$ needed $})");
    }
}
```

*Listing 17-1: caps.lpscr reports its capabilities*

With the default permissions:

```sh
loup run caps.lpscr
```

```text
granted: ["entropy", "fs", "proc"]
fs: yes
net: no (run with --allow-net)
```

With `loup run --allow-net caps.lpscr`, the list becomes `["entropy", "fs", "net", "proc"]`. (`--allow-all` also reports names such as `interop`, `ui` and `eval` that belong to features this chapter and Chapters 14 and 15 describe as planned; holding the capability does not make the feature exist.)

A tool that needs the network can refuse to start without it:

```loup
@mode(host.scripted)

class ConfigError : Exception {}

if (not runtime.has("net")) {
    throw ConfigError("this tool needs network access: run it with --allow-net");
}
println("starting");
```

*Listing 17-2: needs_net.lpscr stops early without the net capability*

Run without `--allow-net`, it stops before doing any work:

```sh
loup run needs_net.lpscr
```

```text
Uncaught ConfigError: this tool needs network access: run it with --allow-net
  at <main> (needs_net.lpscr:6:11)
```

With `loup run --allow-net needs_net.lpscr` it prints `starting`.

### Mistake: `runtime.fs` as a property

The Language Reference (§2-5.1) writes capability namespaces as properties — `runtime.fs.readText(path)`. In Loup 1.25 that compiles and then fails when it runs:

```loup,throws
@mode(host.scripted)

writeText("greeting.txt", "hello from a file");
let text = runtime.fs.readText("greeting.txt");
println(text);
```

```text
Uncaught RuntimeError: internal interpreter limitation: no field `fs on class system.Runtime`
```

The prelude functions are the simplest way to work with files in a script, and they are what the rest of this book uses:

```loup
@mode(host.scripted)

writeText("greeting.txt", "hello from a file");
let text = readText("greeting.txt");
println(text);
```

```text
hello from a file
```

(The method form `runtime.fs().readText(path)` also works today, but it is not the specified spelling.)

## The Planned Design

The Language Reference places JavaScript interop on targets that have a JavaScript runtime: `extern js` on `browser`, `node`, `bun` and `deno`; `@jsexport` on `browser` and `service`. It is mostly web material, and [The Loup Web Book](../loup-web/README.md) covers the browser side. The essentials:

### Calling JavaScript: `extern js`

An `extern js` declaration gives a JavaScript function or value a Loup type (§2-3.1). Declarations end with `;`. The declared types are trusted, not checked, so a mismatch is a run-time error. A function that returns a JavaScript `Promise` is declared to return `Task[T]`:

```loup,ignore
@mode(web.scripted)

extern js func parseInt(s: string, radix: int): int;
extern js func fetch(url: string, opts: Any): Task[Any];
extern js val document: Any;

let port = parseInt("8080", 10);
println(port + 1);
```

*Listing 17-3: Declaring and calling JavaScript functions (design sketch)*

`extern js` in a `host.native` or `freestanding` file is `E_EXTERN_JS_NOT_NATIVE`; native code talks to C through `extern native` instead ([The Loup Native Book](../loup-native/README.md)). An `extern js` declaration that passes a typed array backed by a `SharedArrayBuffer` must carry `@unsafe("sharedMemory: …")` explaining who shares it.

### Exposing Loup: `@jsexport`

`@jsexport` makes a module-level function callable from JavaScript as a named export of the compiled module (§2-3.2). An `async func` becomes a function returning a `Promise`:

```loup,ignore
@mode(web.compiled)

from system.json import (parse);

@jsexport
func countKeys(json: string): int {
    return match parse(json) {
        Ok(v) => (v as dict[string, Any]).length(),
        Err(_) => -1,
    };
}

@jsexport
async func summarize(text: string): Task[string] {
    return i"{$ text.split(" ").length() $} words";
}
```

*Listing 17-4: Two functions exported to JavaScript (design sketch)*

Arguments are converted from JavaScript to the declared Loup types; a value that cannot be converted raises `FfiTypeError`. `await` in a non-`async` export is `E_JSEXPORT_AWAIT`, and `@jsexport` on a target other than `browser` or `service` is `E_JSEXPORT_NOT_BROWSER`.

### Typed facades: `lang.interop.js`

Rather than hand-writing `extern js` declarations, the design provides typed wrappers for the common Web and Node APIs (§2-3.3): `fetch` returning a `Response` with `status`, `ok`, `text()` and `json()`; `setTimeout`, `setInterval` and their `clear…` functions; and, in the browser only, `querySelector`, `Element` and `Event`:

```loup,ignore
@mode(web.scripted)

from lang.interop.js import (fetch);

async func status(url: string): Task[int] {
    let response = await fetch(url);
    return response.status;
}
```

*Listing 17-5: The typed fetch facade (design sketch)*

The specification recommends these facades over raw `extern js` declarations wherever they cover what you need.

## Summary

- In Loup 1.25, scripts run on an interpreter on the host operating system, not on Node, Bun, Deno or a browser, so there is no JavaScript to call.
- `extern js` calls do not compile, `@jsexport` is ignored, and `lang.interop.js` does not exist. Use the standard library for files, processes, JSON and sockets.
- `runtime.capabilities()` and `runtime.has(name)` work today and let a script check its permissions up front.
- Write `readText(path)` (or `runtime.fs().readText(path)`); the specified property form `runtime.fs.readText(path)` fails at run time.
- Planned: `extern js` declarations with trusted types and `Task[T]` for promises, `@jsexport` for `browser` and `service` targets, and the typed `lang.interop.js` facades.
