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 types (string, number, boolean, null, undefined, symbol) all expose a .value property holding the native equivalent. Object-like types do not.
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 obj = realm.types.object({
foo: {
value: realm.types.number(42),
enumerable: true,
configurable: false,
writable: false,
},
});
// Then expose it to the sandbox:
realm.global.setSync("myObj", obj);
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.
return realm.types.number(a.value + b.value);
}),
);
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)) {
throw new StaticJsRuntimeError(realm.types.error("TypeError", "Arguments must be objects"));
}
const va = yield* a.getEvaluator("value");
const vb = yield* b.getEvaluator("value");
if (!isStaticJsNumber(va) || !isStaticJsNumber(vb)) {
throw new StaticJsRuntimeError(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. 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);
Native objects converted this way are not mutable from the sandbox. Property setters are not invoked and properties appear non-configurable. Getters and function calls still work, including the risk of synchronous deadlocks. See Using synchronous functions below.
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.