# js

> Run JavaScript from OSL in a sandboxed QuickJS runtime with a time limit and a memory cap, and get the result back as an OSL value.

This is the Markdown version of https://osl-lang.org/packages/js. Every page of the OSL docs has one: add .md to its path. https://osl-lang.org/llms.txt lists them all.

```osl
import "std:js"
```

`std:js` runs JavaScript code inside your program. It uses QuickJS, a small JavaScript engine, in a sandbox: the code can't reach files, the network or your program, it has a time limit and a memory cap, and each call starts with a fresh runtime. The value of the last expression comes back to OSL.

Use it to run a user's formula or rule, or a small JavaScript library on data you pass in, without trusting the code. For Lua, see [lua](/packages/lua.md).

## Evaluating an expression

`js.evaluate(code)` returns a result. On success it holds the value of the last expression:

```osl
import "std:js"

log js.evaluate("1 + 2").unwrap()
log js.evaluate("'x'.repeat(3)").unwrap()
log js.evaluate("[1, 2, 3].reduce((a, b) => a + b)").unwrap()
```

Output:

```text
3
xxx
6
```

Code can be a whole program. Whatever its last expression evaluates to is the result:

```osl
import "std:js"

string code = `
  const fib = n => n < 2 ? n : fib(n - 1) + fib(n - 2);
  fib(20)
`
log js.evaluate(code).unwrap()
```

Output:

```text
6765
```

## How values convert

The value crosses over as JSON. Numbers, strings, booleans and `null` keep their type, arrays become OSL arrays and objects become OSL objects. `undefined` becomes `null`, and a promise is waited for:

```osl
import "std:js"

auto value = js.evaluate(`({name: "Ada", tags: ["a", "b"], n: null, u: undefined})`).unwrap()
log value.name
log value.tags.len
log value

log js.evaluate("[1, 2, 3].map(n => n * 2)").unwrap()
log js.evaluate("undefined").unwrap()
log js.evaluate("Promise.resolve(5)").unwrap()
```

Output:

```text
Ada
2
{"n":null,"name":"Ada","tags":["a","b"]}
[2,4,6]
null
5
```

Because a result is JSON, functions, dates and class instances do not survive the trip: turn them into plain data inside the JavaScript first.

## Passing data in

A run has no variables from your program. Pass data by writing it into the code as JSON, which is valid JavaScript. Build the text with `json.stringify` so quotes and special characters are escaped properly, never by pasting raw strings:

```osl
import "std:js"
import "std:json"

string[] names = ["Ada", "Grace", "Linus"]
string code = "const names = " ++ json.stringify(names) ++ `;
  names.filter(n => n.length > 3).map(n => n.toUpperCase())`
log js.evaluate(code).unwrap()
```

Output:

```text
["GRACE","LINUS"]
```

A user-written formula works the same way: give the formula and its inputs in one program.

```osl
import "std:js"
import "std:json"

def calculate(string formula, object inputs) result<any, error> (
  string code = "const input = " ++ json.stringify(inputs) ++ "; (" ++ formula ++ ")"
  return js.evaluate(code)
)

log calculate("input.price * input.quantity * 1.2", {price: 10, quantity: 3}).unwrap()
log calculate("input.price *", {price: 10}).isErr()
```

Output:

```text
36
true
```

## When the code fails

Every failure is an error with a kind. `ParseError` is a syntax error, `RuntimeError` is an exception the code threw (or running out of memory), and `TimeoutError` means it ran too long:

```osl
import "std:js"

auto syntax = js.evaluate("let = ;")
log syntax.unwrapErr().kind

auto thrown = js.evaluate("throw new Error('boom')")
log thrown.unwrapErr().kind
log thrown.unwrapErr().message
```

Output:

```text
ParseError
RuntimeError
js.evaluate: Error: boom
```

## Limits

`timeout` (milliseconds, default 60000) and `memoryLimit` (bytes, default 1048576, one mebibyte) bound each run. Use a short timeout for code you don't trust, so an endless loop can't hold your program:

```osl
import "std:js"

auto endless = js.evaluate("while (true) {}", {timeout: 100})
log endless.unwrapErr().kind

auto big = js.evaluate("new Array(10000000).fill(1)", {memoryLimit: 100000})
log big.unwrapErr().kind
```

Output:

```text
TimeoutError
RuntimeError
```

A timeout or memory limit of 0 or less throws a `ValueError`.

## What the sandbox lacks

The code has the JavaScript language and its built-ins (`JSON`, `Math`, `Array`, `Promise` and so on), and nothing else. There is no `require`, `fetch` or `process`, no files and no network:

```osl
import "std:js"

log js.evaluate("typeof require + ' ' + typeof fetch + ' ' + typeof process").unwrap()
```

Output:

```text
undefined undefined undefined
```

> **Note**
>
> `typeof` on a name that doesn't exist is `"undefined"`, which is how the code above probes for them without throwing.

**Quiz.** JavaScript code in `js.evaluate` runs an endless loop. What happens?

- [ ] Your program hangs.
- [x] It stops with a `TimeoutError` result once the timeout passes.
- [ ] The loop is skipped.

Answer: The default timeout is a minute. Pass a shorter `timeout` for code you don't trust.

### Exercise: Sum in JavaScript

Pass `nums` into JavaScript as JSON and have `reduce` add them up. Print the sum.

Starting code:

```osl
import "std:js"
import "std:json"

number[] nums = [4, 8, 15]

log js.evaluate("1 + 1").unwrap()
```

It should print:

```text
27
```

Hint 1: Build the code as text: `"const nums = " ++ json.stringify(nums) ++ "; ..."`.

Hint 2: `nums.reduce((a, b) => a + b)` is the last expression, so it is the result.

A solution:

```osl
import "std:js"
import "std:json"

number[] nums = [4, 8, 15]

string code = "const nums = " ++ json.stringify(nums) ++ "; nums.reduce((a, b) => a + b)"
log js.evaluate(code).unwrap()
```

## API reference

## API reference

Every member of `std:js`, generated from the source of OSL.

### Functions

#### `js.eval(any code, any timeoutMs) object`

**Deprecated.** Deprecated: use js.evaluate(code, {timeout: ms}) instead, which returns the value itself and fails with a typed error.

eval runs JavaScript with a timeout in milliseconds (0 or less is 60000,
and at most 300000) and returns {success: true, result} with the value
as a string, or {success: false, error}.

#### `js.evaluate(string code, object? options) result<any, error>`

evaluate runs JavaScript in a fresh sandboxed QuickJS runtime and
returns the value of its last expression, converted through JSON:
objects, arrays, strings, numbers, booleans and null keep their shape,
undefined becomes null, and a promise is awaited. The code can't reach
files, the network or the program. Options: timeout in milliseconds
(default 60000) and memoryLimit in bytes (default 1048576). Fails with
ParseError for a syntax error, TimeoutError when the code runs too long,
and RuntimeError for an exception it throws or running out of memory.
Throws ValueError for a timeout or memoryLimit of 0 or less.
