Packages

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.

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.

Evaluating an expression#

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

main.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()
Click the code to edit it

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

main.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()
Click the code to edit it

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:

main.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()
Click the code to edit it

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:

main.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()
Click the code to edit it

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

main.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()
Click the code to edit it

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:

main.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
Click the code to edit it

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:

main.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
Click the code to edit it

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:

main.osl
import "std:js"

log js.evaluate("typeof require + ' ' + typeof fetch + ' ' + typeof process").unwrap()
Click the code to edit it
Quick check

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

Your turn

Sum in JavaScript

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

main.osl
Ctrl Enter runs
Expected output
27

API reference#

2 members, generated from the std:js source of OSL 9.3.0.

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.