Skip to main content

Runtime Types

Every value inside a StaticJs sandbox is a StaticJsValue. The type system mirrors JavaScript's own primitives and objects, but wraps them so the host can inspect, create, and manipulate sandbox values without executing untrusted code directly.

For the complete type hierarchy, type guard index, and per-type API reference, see Types.

The type factory

Each realm exposes a types property for creating sandbox values directly, without coercion or round-tripping through evaluation.

import { StaticJsRealm } from "@suntime-js/core";

const realm = StaticJsRealm();

const num = realm.types.number(42);
const str = realm.types.string("hello");
const bool = realm.types.boolean(true);
const nil = realm.types.null; // singleton
const undef = realm.types.undefined; // singleton

For the full factory API including object, array, function, symbol, error, and proxy, see the StaticJsTypeFactory reference.

Narrowing with type guards

Every type ships a corresponding guard function importable from @suntime-js/core. Use these to narrow a StaticJsValue after evaluation:

import { isStaticJsString, isStaticJsNumber, isStaticJsObject } from "@suntime-js/core";

const result = realm.evaluateScriptSync(`Math.random() < 0.5 ? "heads" : 1`);

if (isStaticJsString(result)) {
console.log("Got string:", result.value);
} else if (isStaticJsNumber(result)) {
console.log("Got number:", result.value);
}
Scalar values

All scalar types in StaticJs have a 'value' property containing the host-native javascript value of the scalar.

See the type reference table for all guards.

Creating objects

Pass a property descriptor map to realm.types.object(). Every value in the descriptor must be a StaticJsValue; use the factory (or toStaticJsValue) to produce them:

const proto = realm.types.object({
foo: {
value: realm.types.number(42),
enumerable: true,
configurable: false,
writable: false,
},
});

const obj = realm.types.object({}, proto);

An optional second argument sets the sandbox prototype. If omitted, Object.prototype from the realm's intrinsics is used.

For accessor descriptors, get and set must be StaticJsFunction values, not native functions.

Creating functions

Use realm.types.function(name, callback, options?) to expose host logic to the sandbox:

realm.global.setSync(
"add",
realm.types.function("add", (a, b) => {
// Arguments arrive as StaticJsValues.
const sum = a.value + b.value;

// Return value must be a StaticJsValue
return realm.types.number(sum);
}),
);

For functions that need to participate in time-sharing (i.e. can be paused and resumed by the task runner), define the callback as a generator function and delegate internal *Evaluator calls with yield*:

import { isStaticJsObject, isStaticJsNumber, StaticJsRuntimeError } from "@suntime-js/core";

const compare = realm.types.function("compare", function* (a, b) {
if (!isStaticJsObject(a) || !isStaticJsObject(b)) {
// Thrown StaticJsValue objects will be capturable by the sandbox
// code through try / catch.
throw realm.types.error("TypeError", "Arguments must be objects");
}

// Use evaluator functions so the get operations share the same
// task runner and time slicing as the caller.
const va = yield* a.getEvaluator("value");
const vb = yield* b.getEvaluator("value");

if (!isStaticJsNumber(va) || !isStaticJsNumber(vb)) {
throw realm.types.error("TypeError", "Object values must be numbers");
}

return realm.types.number(va.value - vb.value);
});

realm.global.setAsync("compare", compare);

Generator functions that are used in the service of evaluating sandboxed code are called Evaluators. See Evaluators for detailed instructions on how they work and how to write them.

Coercing native values

realm.types.toStaticJsValue(nativeValue) converts a host value to a sandbox value following the coercion rules and the realm's default host access policy. This is a convenience shortcut for passing existing host objects or functions into the sandbox:

const wrapped = realm.types.toStaticJsValue({
greet(name) {
return `Hello, ${name}!`;
},
});

realm.global.setSync("host", wrapped);
caution

By default. Native objects converted this way are partially immutable and restricted from the sandbox. Only enumerable properties will be revealed, properties will not be configurable, and data properties will not be writable. Property setters, however, will still be invoked, although their this-arg are pinned to the host object. Functions passing through this barrier (such as sandboxed functions passed as a callback argument to a host function) will be invoked synchonously, which opens the risk for deadlocks.

See Type Coercion for more details, including how to customize this behavior, and Using synchronous functions below for how to handle deadlocks.

Reading object properties

StaticJsObject exposes all ECMAScript internal methods as async/sync/evaluator triplets. Prefer the *Async variants in host code:

const val = await obj.getAsync("foo");
const keys = await obj.ownPropertyKeysAsync();
await obj.setAsync("bar", realm.types.number(99));

The *Sync variants work but share the same deadlock caveats as synchronous evaluation. The *Evaluator variants are for use inside generator callbacks only.

Using synchronous functions

Several APIs execute sandboxed code synchronously. If that code contains an infinite loop, the host deadlocks. Examples of such functions are toStringSync(), toNative(), and the *Sync() family of object methods

Guard against this by passing a runTask option with a time limit:

const name = func.getNameSync({
runTask: createTimeBoundTaskRunner({ maxRunTime: 1_000 }),
});

For synchronous task runners, the runner must fully drain the iterator or call .abort() / .throw(). Failing to do so throws a StaticJsSynchronousTaskIncompleteError.

See Tasks for more on task runners.

Coercing sandbox values to native

All StaticJs values have a toNative() method that produces a host-proxied representation.

const funcSrc = realm.evaluateScriptSync(`
function add(a, b) {
return a + b;
}
add;
`);

const func = funcSrc.toNative();

const result = func(1, 2);
toNative() and objects

While toNative is safe for scalar values, it returns live proxies for objects. Such proxies carry risks, as accessing any property on it may synchronously invoke sandboxed code. Avoid toNative() on objects unless you have a time-bounded StaticJsRealm.runTaskSync configured.

Additionally, toNative is by nature bidirectional, and will coerce native values to sandboxed ones through function arguments. This carries the same risks as Coercing native values above.

Prefer .value on scalars (accessible after an isStaticJsScalar check) and direct StaticJsObject API methods on objects.

See Type Coercion for full coercion rules.